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?
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.
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.
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
.
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.
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
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.