Raw pointers (*const and *mut) don't take lifetime annotations because they aren't checked by the borrow checker. In this case, you have a raw pointer coming from FFI that is valid as long as another struct is, and you bind them together using lifetimes while also wrapping the pointer into a struct. The PhantomData is needed to tell the compiler how the lifetime parameter should behave, because the *const doesn't. Without it, it is unclear whether the *const is a borrow or an owning pointer, but for an owning pointer there would be no lifetime parameter, so it wouldn't make sense to include it in the pointer syntax. While there could probably be a more compact syntax, PhantomData is a standard way of doing these sorts of things, and making it more explicit what is going on is helpful when dealing with FFI and unsafe code.
There isn't one single &str type. There's an infinite number: &'a str, &'b str, ..., &'static str. &str is a type constructor parameterized by a lifetime.
But there's only one *const str type. And if it was parameterized, *'static const str wouldn't be a replacement for most the roles *const str plays today, as it would imply the referent is valid for the entire program (i.e. typically static or leaked memory).
(Even ignoring the roles bit, type constructors are more challenging to work with than concrete types in a number of ways.)
There could conceivably lifetime parameterized raw pointers, but they would be a new thing, not something that can be simply tacked onto the existing *const _ and *mut _ types. And as lifetime-parameterized and non-lifetime-parameterized raw pointers don't satisfy the same roles, even if lifetime-parameterized versions had existed from the start, non-lifetime parameterized versions would have almost surely existed as well.
As a thought experiment, maybe the two could be unified via some new special 'ffi or 'unsafe lifetime, but in practice I don't think that would play nice for things like blanket implementations.
But 'a doesn't describe a type to the best of my knowledge, just lifetime that that object of that type is bound by.
So really the type behind *const/*mut is irrelevant to this discussion
All I am saying is that instead of using that PhantomData we could add the lifetime to the object we care about and the reason for having lifetime:
'a *const T
//EDIT
TBH, it could be even syntactic sugar that compiler seeing 'a *const/*mut T would add PhantomData to struct.
But then, we know that phantomdata is removed during the compilation, so actually adding 'a *const T makes sense as the info for compiler is there. Exactly the same info. Just much more cleanly and intuitively expressed.
Absolutely! And then there would be one tiny, insignificant detail: this wouldn't be one type, anymore, but infinite number of types. Which means that this type would no longer be usable for FFI.
I'm not sure if lifetime is actually called a type in Rust, or not, but any type with non-trivial lifetime is no longer a single type, but infinite series of types.
Rust, basically, have dependent types, in a disguise: any type that have some non-trivial lifetime attached to it is a generic type and thus couldn't be used with FFI (because C ABI doesn't have generic types, thus couldn't handle them).
And if you don't want to use these types with FFI, then you don't need pointers, PhantomData and other dances around them, you can just use references.
It's not exactly the same, BTW. Lifetimes have variance. Which means that you would need to invent some kind of specifying it in your “pointers with a lifetime”.
All these things are not impossible, just Rust wasn't really designed to make life of FFI-users easy (at the expense of everything else becoming harder). That's why it reuses already existing PhantomData instead of designing yet another subsystem made just for the need to people who are working with pointers in an FFI setting.
Can Rust be more complicated to simplify FFI code? Perhaps. Would that be good trade-off? I don't think so.
The signature of get_element contains a lifetime, and that’s okay, because the lifetime is erased and has no effect on linking or run-time behavior (and the layout of references is identical to the layout of raw pointers[1]). Certainly there are many, many C interfaces where there aren't lifetimes you can correctly put on their function signatures, or other reasons why references should not be used (e.g. if there would be aliasing), and thus we often use raw pointers in FFI, but there is no rule that says lifetimes cannot appear in FFI.
But we can go further. That SSL_CIPHER is typically used as a context pointer with its own associated functions. As is, having our safe code return a reference to a C struct isn’t ergonomic at all. What we want to return is a Rust struct with its own associated behavior matching the C library.
And here's another approach to that general scenario.
#[repr(transparent)]
pub struct Cipher {
cipher: ffi::SSL_CIPHER,
}
// ...
pub fn get_cipher<'a>(&'a self) -> &'a Cipher {
// SAFETY: ..., and `Cipher` is `repr(transparent)`.
unsafe {
let cipher = ffi::SSL_CTX_get_cipher(self.ctx) as *const Cipher;
&*cipher
}
}
Wrong. It would be one type. It would just have in this scenario lifetime attached to it. Just like it has with phantomdata. Except without phantomdata. Which is exactly the end result of the compilation. There isn't any phantomdata.
But what lifetime will it have attached? If the answer is "the generic parameter", then that's not "one type", but "one type per instantiation of parameter".
It is one type. The type T to which *const points to doesn't change if we attach lifetime to it or not.
Same way the type T doesn't change if we accessing it via reference &. It is the way we accessing it that changes but that way doesn't change the type itself.
Also, the *const type doesn't suddenly change. It is still *const. The only difference is that has a lifetime attached to it.
But you don't attach lifetime to T - that would be *const T<'a>, and that's already legal if T is parametrised. Similarly, when you take a borrow of T, you don't attach anything to T itself - the lifetime in &'a T is a property of this specific borrow.