Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Nice list!

This is a nitpick on my part, but this part on PhantomData:

> Tells the compiler that Foo owns T, despite only having a raw pointer to it. This is helpful for applications that need to deal with raw pointers and use unsafe Rust.

...isn't quite right: `_marker: marker::PhantomData<&'a T>,` doesn't tell the compiler that `Foo` owns `T`, but that instances of `Foo` can't outlive its `bar` member. `bar` is in turn borrowed, since a pointer is "just" an unchecked reference.

You can see this in the playground[1]: `foo1` and `foo2` have the same borrowed `bar` member, as evidenced by the address being identical (and the borrow checker being happy).

Edit: What I've written above isn't completely correct either, since the `PhantomData` member doesn't bind any particular member, only a lifetime.

[1]: https://play.rust-lang.org/?version=stable&mode=debug&editio...



Not quite, it doesn’t tell the compiler much about how Foo relates to its bar field unless you also constrain the public API for creating a Foo. If Foo is constructed out of a ‘new’ method with this signature:

    impl<'a, T> Foo<'a, T> {
        pub fn new(bar: &'a T) -> Self {
            Self { bar, _marker: PhantomData }
        }
    }
… AND the bar field is private, then you are ensuring the Foo container doesn’t outlive the original bar pointer. If you don’t constrain creation and access to the bar field then people can just write foo.bar = &new_shorter_borrow as *const T; and then the lifetimes are absolutely unrelated, foo.bar will become invalid soon. A classic example of doing this correctly is core::slice::IterMut, and the slice.iter_mut() method.

A short illustration (note how only one of the Foos creates a compile time error): https://play.rust-lang.org/?version=stable&mode=debug&editio...

A short explanation of what you’re telling the compiler with PhantomData (without accounting for the new method/ constraints on creation) is that Foo appears to have a &'a T field, for the purposes of the outside world doing type and borrow checking on Foo structs and their type parameters. That does two things:

(1) Due to implied lifetime bounds, Foo’s generics also automatically become struct Foo<'a, T: 'a>, so this ensures T outlives 'a. That will prevent you accidentally making Foo<'static, &'b str>.

(2) You don’t have an unused lifetime, which is an error. We always wanted Foo to have a lifetime. So this enables you to do that at all.

Edit: and (3) it can change the variance of the lifetimes and type parameters. That doesn’t happen here.


Genuinely asking: what about this is different from what I said in the original comment? It's possible that I'm using the wrong words, but my understanding of Rust's ownership semantics is that "owns a reference" and "borrows the underlying value" are equivalent statements.


You can have a PhantomData without a bar field. You could have baz and qux fields as well. The compiler does not know the lifetime in the phantom data is connected to any other field whatsoever. It doesn’t look through the other fields searching for *const T. It’s just a lifetime and is connected to T, not any particular pointer to T. The bar field happens to have a T in it, so the T: 'a constraint does apply to what you store inside the pointer, but the *const T is lifetime-erased as it relates to the pointer, so you have to ensure that the lifetime of the *const T matches up with the lifetime your Foo type claims it has (in its documentation). You must do that manually by constraining the struct creation and field access. You would also typically have a safe method to turn bar back into a borrow in some way (like IterMut::next does) and when you do, you will want to be sure that the pointer is actually valid for the lifetime you say it is.*


Thanks for the explanation! I can see how my nitpick wasn't right then, either. I'm going to make an edit noting that.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: