How to create instances of user-defined unsized types?

This is a valid Rust program that compiles:

struct UnsizedStruct {
    unsized_field: [i32],
}

I can define an UnsizedStruct but how do I actually create one?

Follow-up question: what are the use-cases of user-defined unsized types?

I think (though I may be mistaken) that the only way to instantiate a custom unsized type in safe/stable Rust is to make it generic, and use unsized coercion to cast a pointer from a sized type to an unsized type:

struct UnsizedStruct<T: ?Sized> {
    unsized_field: T,
}

fn main() {
    let x: UnsizedStruct<[u8; 4]> = UnsizedStruct { unsized_field: [0; 4] };
    let r: &UnsizedStruct<[u8]> = &x;
}

This example casts a reference, but it also works with Box or Rc or any other type that implements CoerceUnsized.

3 Likes

If you have exactly one field, and are OK with unsafe code, you can also mark it with #[repr(transparent)] and then pointer cast from &[i32] to &UnsizedStruct. For example, std::path::Path does this.

2 Likes

Does working with an

struct UnsizedStruct<T: ?Sized> {
    unsized_field: T,
}

or a

#[repr(transparent)]
struct UnsizedStruct {
    unsized_field: [i32],
}

offer any advantage over the more obvious

struct Struct {
    sized_field: Vec<i32>,
}

???

I think the biggest advantage is that you can have a borrowed version which is semantically similar to an owned one? Like with UnsizedStruct, both Box<UnsizedStruct> and &UnsizedStruct exist, and have no overhead, whereas &Struct is a pointer to a pointer. You can also define exactly one struct, and get many variations for free - &UnsizedStruct, &mut UnsizedStruct, Box<UnsizedStruct>, Arc<UnsizedStruct> all just exist naturally, whereas if you define Struct, and want the same variations, you'll need to define StructRef, StructMutRef, and ArcStruct, etc.

The other advantage is an intuitive one - std::path::Path is just an unsized type, and you use it like &Path, rather than having Path<'a>. It's first class, and for learning can be compared to str, rather than being a weird thing storing a reference.

I think the advantages are much more pronounced if you plan to be passing this thing around by reference more often than by ownership. Even with Path, we don't use Box<Path> for the owned variation, we use PathBuf instead. If you'll be owning it the majority of the time, just go with a sized structure and store a Vec.

3 Likes

Overall, I feel like user-defined unsized types are still a sort of half-baked feature. They might become more usable in the future, but for now I think the limitations often outweigh the advantages.

4 Likes

Yeah, I'm aware user-defined unsized types are pretty unusable, but I'm doing research for a blog post and want to include them for the sake of being comprehensive :slight_smile:

std::path::Path is really interesting! They could chosen to define it like this:

pub struct Path {
    inner: OsString,
}

But instead they chose:

pub struct Path {
    inner: OsStr,
}

I'm guessing the reason probably has something to do with what @daboross said about having a single pointer to the data instead of a pointer to a pointer.

I'm also guessing the reason for wrapping OsStr in a Path in the first place is to define a bunch of path-related methods on it that otherwise wouldn't make sense on a general OsStr.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.