3

My mental model of data layout in Rust was that all structs' sizes have to be known at compile-time, which means all of their properties have to be known at compile-time recursively. This is why you can't have a struct member that's simply a trait (and why enums have to be used in place of union types): the size can't be known, so instead you have to use either

  1. A generic, so the trait gets "reified" to a size-known struct at usage time
  2. An enum, which has a finite set of possible size-known layouts
  3. A Box, whose size is known because it's just a pointer

But in the docs for Path, it says:

This is an unsized type, meaning that it must always be used behind a pointer like & or Box. For an owned version of this type, see PathBuf.

Yet Path is neither a trait nor a generic struct, it's just a plain struct.

What's wrong with my mental model that this can be possible?

I found this explanation of what dynamically-sized types are, but I still don't understand, how I would make one of my own. Is doing so a special privilege reserved for the language itself?

5
  • 1
    "You can declare a struct (or enum or tuple) [...], containing an unsized type. A type containing an unsized type will be unsized too." Commented Apr 18, 2020 at 17:21
  • In addition to what @E_net4isnotamoderator said: Path contains a OsStr which is a slice of memory (without a defined length). So as the content is unsized, the Path struct is unsized as well. Commented Apr 18, 2020 at 17:27
  • @E_net4isnotamoderator sort of. I'm still fuzzy on what these types actually look like in memory, and why they deserve their own special status instead of just being traits. Commented Apr 18, 2020 at 17:40
  • They are different things: traits only define behaviour, whereas dynamically sized types (DSTs) exist as concrete values, in a way which can only be manipulated behind a pointer because their size is not constrained. The relation between the two is that trait types (dyn Trait) are always DSTs, the opposite isn't true. Building your own unsized struct and making it useful is a complicated subject with many requirements and few benefits. Commented Apr 18, 2020 at 17:53
  • You can also create a struct struc Foo {bar:str} or (bar:[u8]) which is unsized. I would expect that rust optimizes it to contain just the contents of bar. And since it is unsized, you cannot assign it to a local (stack) variable. On the heap variable sizes are manageable (as with strings and slices). Commented Apr 18, 2020 at 17:59

1 Answer 1

5

A struct is allowed to contain a single unsized field, and this makes the struct itself unsized. Constructing a value of an unsized type can be very difficult, and can usually only be done by using unsafe to cast a reference to a sized variant to the unsized variant.

For example, one might use #[repr(transparent)] to make it possible to cast an &[u8] to a &MySlice like this. This attribute makes the type guaranteed to be represented in the same way as its single field.

#[repr(transparent)] struct MySlice { inner: [u8], } 

It would then be sound to convert slices like this:

impl MySlice { pub fn new(slice: &[u8]) -> &MySlice { // SAFETY: This is ok because MySlice is #[repr(transparent)] unsafe { &*(slice as *const [u8] as *const MySlice) } } } 

You could e.g. refuse to perform the conversion if the slice was not valid ascii, and now you would have a byte array that was guaranteed to point to valid ascii, just like how &str is an &[u8] that is guaranteed to be valid utf-8.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.