Such a situation can occur when pulling &str
out of containers, and you end up with a &&str
. Is it always safe (and possible?) to reduce some arbitrary amount of reference layering down to a single one (in this case, a &&str
to a &str
)?
That's doable in safe rust (****r
), so this is necessary always safe.
Care must be taken to use the best lifetime though. &'a &'b str
can be reduced to either &'a str
or &'b str
. The latter is more general. Sometimes it's not clear what exactly is happening due to lifetime elision.
Is *
the same as .deref()
? *
looks somehow dangerous and copyish to my eyes, given that *
on primitives does a copy. Should I trust *
more?
The simple answer is "yes, * is deref, and that's why you shouldn't do .deref()
". The "well, actually" answer will involve deref coercions and other fun facts about method resolution.
Should I trust * more?
Yes. More generally, if you don't touch unsafe
, the worst that can happen to you is coding yourself in a corner with wrong lifetime choices, and a long refactoring thereafter to thread the proper 'a
everywhere.
copyish to my eyes, given that
*
on primitives does a copy.
This is exactly what is happening here.
fn f<'a>(x: &&'a str) -> &'a str {
*x
}
copies-out the inner reference, exactly the same way as if x
were an &usize
. &T
is a Copy
type.
Is a reference just a cheap struct holding a raw ptr, etc? Is there a Rustonomicon section I could look at to learn more?
I am sure they are implemented as pure pointers.
The reference is a built-in type. You can think about it as
struct Ref<'a, T> {
ptr: core::ptr::NonNull<T>,
}
impl<'a, T> Cloe + Copy for Ref<'a, T> { }
It will be almost accurate, but there's a snag preventing this from working -- you'll need phantom data to explain variance of &'a
by example, and you'll end up with a circular definition
struct Ref<'a, T> {
ptr: core::ptr::NonNull<T>,
// urgh, I am trying to *define* a `&'a T` :-(
ghost: core::marker::PhantomData<&'a T>,
}
But, still, "reference is type ctor with one type parameter -- lifetime" is a useful intuition.
I don't think there's a great single source to learn such low-level details, but just looking at std::mem::size_of
of things can often illuminate their inner workings.
Just for completeness sake: When the target type is clear to the compiler, coercing from &&..&T
to &T
can happen totally implicitly in Rust, too:
fn foo<'a, T>(x: &&&&&'a T) -> &'a T {
x
}
fn bar() {
let x: &str = "hello";
let y: &&&str = &&x;
let z: &str = y;
}
fn f(_: &str) {}
fn baz() {
let x: &&str = &"hi";
f(x)
}
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.