Lifetime conversion explanation Why is allowed?

I got a question how does this compile.

fn this_function<'a, 'b>(arg: &'a mut &'b u8) -> 
&'b mut &'a u8
{
    let a: &'a mut &'a u8 = arg;
    a
}

Is there some implicit lifetime bound, why able to “swap” 2 lifetimes
Also if we introduce 2 new lifetimes

fn functionthing<'a, 'b, 'c, 'd>(arg: &'a mut &'b u8) -> 
&'b mut &'a u8
where
'b: 'c, 'c: 'b,
'b: 'd, 'd: 'b,
{
    let a: &'d mut &'c u8 = arg;
    a
}

but at the bounds you can replace the bounds to

‘b: ‘c, ‘c: ‘a,
‘b: ‘c, ‘c: ‘b

No compile error so extrapolating to (and changing &’c … to &’d …)

fn functionthing<'a, 'b, 'c, 'd>(arg: &'a mut &'b u8) -> 
&'b mut &'a u8
where
/*’b or ‘a*/: 'c, 'c: /*’b or ‘a*/,
/*’b or ‘a*/: 'd, 'd: /*’b or ‘a*/,
{
    let a: &/*’c or ‘d*/ mut &/*’c or’d*/u8 = arg;
    a
}

I tested some combination in rust playground but why does this work when compiling,
and does changing bounds, change the usability of this function

&'a mut &'b u8 in the argument type introduces an implicit 'b: 'a bound, and &'b mut &'a u8 in the return type introduces an implicit 'a: 'b bound. Together the result is that 'a and 'b are the same lifetime, so this is the same as

fn this_function<'c>(arg: &'c mut &'c u8) -> &'c mut &'c u8 {
    let a: &'c mut &'c u8 = arg;
    a
}

All the lifetimes are the same given the explicit and implicit bounds. ('a and 'b are still the same, the first two explicit bounds mean 'b and 'c are the same, the last two explicit bounds mean 'b and 'd are the same.)

It's a contrived function signature that doesn't seem useful in the first place, so :person_shrugging:. You generally don't want to add extra bounds if there's no need to.

2 Likes

Okay, but what other types introducing lifetime make implicit bounds like you said

So return type and input types introduces implicit bounds, is there other example where types introduce implicit bounds

Here's a whirlwind introduction to implied bounds.


Outside of dyn types, types can only introduce implicit outlives bounds so far. This happens when you have explicit bounds on the definition...

// Why you would do this specifically, I don't know, but you can:
//                  vvvvv explicit outlives bound which is implied elsewhere
struct Whatever<'a, T: 'a>(&'a str, T);

// Compiles due to the implicit `T: 'a` from mentioning `Whatever<'a, T>`
fn use_implied<'a, T: Display>(we: Whatever<'a, T>) -> Box<dyn Display + 'a> {
    Box::new(we.1)
}

...or when the bound is required for the fields in the definition to be well formed:

// `&'a T` implies `T: 'a`, so the compiler infers that this struct should have
// a `T: 'a` bound.  (Some time ago you would have to make the bound explicit,
// but now you usually don't.)
//
// The `T: 'a` bound is implied by `Whateverr<'a, T>` elsewhere.
struct Whatever<'a, T>(&'a T, T);
fn use_implied<'a, T: Display>(we: Whatever<'a, T>) -> Box<dyn Display + 'a> {
    Box::new(we.1)
}

'static bounds required for well-formedness must be explicit, not inferred.

// Error if you leave this off:
//              vvvvvvvvvv
struct Whatever<T: 'static>(&'static T, T);

// But once you write that explicit bound, `T: 'static` is implied elsewhere.
fn use_implied<T: Display>(we: Whatever<T>) -> Box<dyn Display + 'static> {
    Box::new(we.1)
}

(I can't recall the exact reasoning as to why beyond "it might be confusing". Personally I find the inconsistency more confusing :person_shrugging:.)


When you introduce a generic type parameter T, there's generally an implicit T: Sized bound.

// There's no stable support for taking unsized parameters by value,
// and `Vec<_>` does not support unsized types either.  This compiles
// because there's an implicit `T: Sized`.
fn ex0<T>(t: T) -> Vec<T> {
    vec![t]
}

fn ex1<T>(t: &T) {}
fn main() {
    // This fails because we're passing in a `&str`, so `T` on `ex1`
    // is inferred to be `str` -- but `str` is not `Sized` so the implicit
    // `T: Sized` bound is not met, and we're not allowed to call teh function.
    ex1("");
}

You can remove the implicit bound with ?Sized.

//      vvvvvvvv Removes the implicit `T: Sized` bound.
fn ex1<T: ?Sized>(t: &T) {}
fn main() {
    // This compiles now.
    ex1("");
}

The main kind of implied bounds for traits are bounds on the implementing Self bounds from the definition. These are called "supertrait" bounds because you can syntactically write them as if they were bounds on the trait itself, and because there are coercions from subtrait dyn types to supertrait dyn types. (But note that there is not an actual subtyping relationship.)

trait Trait: Display
where
    // Writing the bound this way means the same as putting the bound
    // on the trait name directly, like we did above
    // vvvvvvvvvvvvv
    // Self: Display,
    //
    // This is an example of a bound which is *not* a supertrait bound
    // (because it is on `Box<Self>` and not `Self`)
    Box<Self>: Debug,
{}

fn example<T: Trait>(t: T)
where
    // The function will not compile if we don't add this bound, because
    // it is not implied.  So it must be restated wherever you have the
    // `T: Trait` bound (unless it's implied by something else).
    Box<T>: Debug,
{
    // Compiles because `T: Display` is implied by `T: Trait`.
    println!("{t}");
    
}

Bounds involving the associated types of supertraits are sometimes implied and sometimes not, based on how you syntactically wrote them.

pub trait One: Iterator
where
    // Not a bound on `Self` and not implied elsewhere
    Self::Item: Display,
{}

// But this associated type bound is implied elsewhere.
//                      vvvvvvvvvvvvv
pub trait Two: Iterator<Item: Display>
{}


fn ex1<I: One>(_: I)
where
    // Does not compile without this bound.
    I::Item: Display,
{
}

// Compiles and `<I as Iterator>::Item: Display` is implied
fn ex2<I: Two>(iter: I) {
    if let Some(item) = iter.last() {
        println!("{item}");
    }
}

Traits do not have an implicit Sized bound.

trait Trait {
    // This gives an error[E0277]:
    // the size for values of type `Self` cannot be known at compilation time
    fn cannot_assumed_implementor_is_sized(&self, _: Self) {}
}

But associated types do.

trait Trait {
    type Assoc;
    // This compiles, but won't if we add `: ?Sized` above.
    fn use_assoc(&self, _: Self::Assoc) {}
}

And explicit bounds on associated types can be implied too:

trait Trait {
    type Assoc: Clone;
    // Didn't have to write `where Self::Assoc: Clone`
    fn use_assoc(&self, a: Self::Assoc) -> Self::Assoc {
        a.clone()
    }
}

That's the end of the whirlwind introduction. Why is it an introduction? Because there are a lot of nuanced rules and corner-cases which I didn't cover. There is no comprehensive guide.

Should I learn more about implicit bounds or better to rely on rust compiler's help message on placing bounds when needed

And if yes, where the resources for this?

I'd say better to rely on the compiler help when you're getting started / until you hit a significant roadblock around them. Ask questions when something specific confuses you.

1 Like

What differs from

and

fn one_bound_but_two_life<'a, ‘b>(arg: &'a mut &'b u8) -> &'a mut &'b u8
{
    arg
}

Since this function uses 2 lifetime with implicit bound, ‘b: ‘a, or both the same in terms of functionality

What I mean by functionality is that see here with same lifetime and different lifetime from this person named “kohugaly” in reddit post

https://archive.is/hb1q6

I believe this is a typo. Did you mean implicit T: Sized bound instead?

Great summary overall!

1 Like

In this new function, there is only one implicit bound, 'b: 'a. The lifetimes don't have to be the same any more. The two functions will act different in practice. Let's look at two significant reasons why.

The first reason is invariance. You never want &'a mut Thing<'a> because, due to invariance, it means that Thing<'a> is borrowed forever -- or in other words, you are exclusively locking Thing<'a> until the 'a lock is released.

Thing<'a> cannot be used after the 'a lock is released, so once you create &'a Thing<'a>, you cannot use Thing<'a> directly ever again.

Here is a demonstration in how they differ due to this invariance consideration:


But even without invariance, the two functions act differently, since only one of them forces the lifetimes to be the same.

If we use &&u8 instead of &mut &u8, both lifetimes are covariant. That means &'a &'b u8 can coerce to &'a &'a u8. The two functions are closer in how they act now.

Let's walk through it:

    let local = 0_u8;
    let mut inner = &local;
    let output = two_life_act_like_one(&inner);
    let __ = &mut inner;

Say the lifetime in the type of inner is exactly 'inner. You can think of the call to two_life_act_like_one as if the &inner between the parenthesis is a &'x &'inner u8, and then it gets coerced to a &'x &'x u8 as per the function signature, and output is also &'x &'x u8. Because we could coerce the inner lifetime, the outer lifetime was not forced to be 'inner. So inner is not shared-borrowed "forever" -- it is only shared for 'x.

Therefore it's not an error to get an exclusive borrow/lock on inner once you are done using outer, even though we made the lifetimes be the same.

The two functions are still not exactly the same though. Let's make this change:

     let mut inner = &local;
     let output = two_life_act_like_one(&inner);
-    let __ = &mut inner;
+    inner = *output;

The assignment to inner requires that the output is a &'? &'inner u8. We're still forcing the two lifetimes to be the same, so that must be a &'inner &'inner u8. Now inner is borrowed forever again, albeit shared-borrowed and not exclusive-borrowed. We can't do anything exclusive with inner ever again -- so we can't overwrite inner.

In other words: even without invariance, something may require the inner lifetime in the output type to be longer than you use the output reference. Here we caused it to be 'inner. Because the two lifetimes must be the same, this causes inner to be "locked" for longer, too.

one_bound_but_two_life doesn't have this problem because it doesn't force the lifetimes to be the same.

Thanks, fixed.

I don’t get this

fn create_restrict<‘a>(arg: &'a mut u8, a: &'a mut u8) -> (&'a mut u8) {
      (arg)
}

Here when using 2 references types from what I understand, dropping 1 will invalidate the other type like this, but why? Another one is that

fn copyee<‘a>(arg: &’a u8) -> (&’a u8, &’a u8) {
     (arg, arg)
}

Dropping 1 (using scopes in a tuple like e.g ...

let tup = copyee(/*... local reference ...*/);
{
    let a = tup.0;
    // Something down below
}
let b = tup.1;

This works without invalidating
My guess due to copying the type and “label” of the lifetime and due to covariance or its it something else?
but what would differ usability if replacing the create_restrict function’s parameter into &mut u8 instead.
Additionally,

fn bad_func<'a, 'b>(arg: &'a mut u8) -> (&'a mut u8, &'a mut u8) {
    (arg, unsafe{&mut *(core::ptr::dangling::<u8>() as usize as *mut u8)})
}

I know 1 of the lifetime is unbounded by dangling reference I am assuming, the information was from the rustonomicon book
(I know deferring this bad mutable reference is Undefined behaviour and the mutable, BAD, reference itself is Undefined behaviour as its break the assumption)
What would differ of its usability if its lifetime was the same like in this example

fn bad_func<'a>(arg: &'a mut u8) -> (&'a mut u8, &'a mut u8) {
    (arg, unsafe{&mut *(core::ptr::dangling::<u8>() as usize as *mut u8)})
}

(In terms of would dropping 1 type invalidate another or something)

Also what differs from naming lifetime in

impl<‘a> SomeType {
    fn method(a: &’a str) {
        // Some work
    }
}

And in the function

fn life<‘a>(&’a u8)

And in the combined

impl<‘a> SomeType {
    fn method2<‘b>(a: &’a str, b: &’b str) {
        // Some work
    }
}

And the lifetime from a struct

struct SomeType<'a>(&'a u8);

impl<'a> SomeType<'a> {
    fn worker_func(arg: &'a str) -> &'a str {
        arg
    }
}

And associated method with self


struct SomeType<'a>(&'a u8);

impl<'a> SomeType<'a> {
    fn worker_func(&self, arg: &'a str) -> &'a str {
        arg
    }
}

(in terms of usability, or something similar)

The create_restrict signature effectively says "the function may return arg or a (or reborrows thereof)". Or alternatively, "uses of the return value keep *arg and *a borrowed". It is the signature that determines this, not the function body. Both the caller and the callee (the function body) must adhere to the signature.

Why is it designed that way? The signature is like a contract, and under that contract, these are all legal function bodies:

fn create_restrict<'a>(arg: &'a mut u8, a: &'a mut u8) -> &'a mut u8 {
    arg
}

fn create_restrict<'a>(arg: &'a mut u8, a: &'a mut u8) -> &'a mut u8 {
    a
}

fn create_restrict<'a>(arg: &'a mut u8, a: &'a mut u8) -> &'a mut u8 {
    match *arg < *a {
        true => arg,
        false => a,
    }
}

And -- in terms of what code compiles -- changing between them is a non-breaking change. The caller must not depend on what goes on in the function body and act like it could be anything the signature allows. (Think SemVer stability for a library.)

Another reason is to avoid global analysis, which is both expensive and very brittle.


Aside from using &_ instead of &mut _, your playground is mostly about the same idea, but with a little twist: it involves a reborrow.

fn restrict<'a>(arg: &'a u8, _dropee: &'a u8) -> &'a u8 {
    arg
}

fn main() {
    let a = 5;
    let mut b = 7;
    let local = &a;
    let localb = &mut b;

    // This reborrows `*localb`, as if you had wrote `&*localb`.
    //
    // It's an error to use `*localb` in an exclusive manner while the
    // shared reborrow is active.
    let tup = restrict(local, localb);

    // A move of `localb` is considered an exclusive use of `*localb`.
    // `*localb = 0` would be another example of a conflicting use.
    drop(localb);

    // A use of the return value which keeps the input borrows -- including
    // the reborrow of `*localb` -- active.
    let c = tup;
}

It's not covariance, there's just no borrow checking conflicts. No annotation constraints could not be proved, and there was no use of values that conflicted with being borrowed.

fn main() {
    let local = 0;
    let tup = copyee(&local); // `local` starts being borrowed.
                              // Uses of the return value will keep it borrowed.
    {
        let a = tup.0; // `local` must still be borrowed here.
    } // `a` goes out of scope but `a` itself is not borrowed, no problem.
      // (`a` going out of scope is otherwise a no-op.)

    let b = tup.1; // `local` must still be borrowed here.

    // Nothing else happens to keep `local` borrowed, so here
    // and beyond `local` is no longer borrowed.

} // `b` goes out of scope but `b` itself is not borrowed, no problem.  
  // (`'b` going out of scope does not keep `local` borrowed, not that
  // it would have mattered for this example anyway.)
  //
  // `local` goes out of scope but it is no longer borrowed, no problem.

create_restrict did have &mut u8s but restrict and copyee didn't, so I'm not sure how to address this part.

The body is UB after which nothing else really matters.[1] But if we ignore the function bodies and just look at the difference in signature...

fn bad_func<'a, 'b>(arg: &'a mut u8) -> (&'a mut u8, &'a mut u8) {
fn bad_func<'a    >(arg: &'a mut u8) -> (&'a mut u8, &'a mut u8) {

...the only difference is an unused 'b in the first signature. That won't matter in practice so the signatures are effectively the same. The "contract" of the two signatures is the same. It says "uses of either of the returned &mut u8s will keep *arg exclusively borrowed."

impl<'a> SomeType { fn   method(a: &'a str) { } }
impl     SomeType { fn life<'a>(a: &'a str) { } }

The impl<'a> version is not idiomatic. You would normally only do that if you needed the lifetime before the {. There's no difference between method and life in terms of what arguments you can pass in.

Leaving that aside, there are differences at the type and trait level -- how the function item type[2] is defined and how it implements the Fn traits. It's a nuanced difference that is probably more distracting than elucidating when you're learning Rust; knowing about it is niche knowledge. But here's a link anyway.


impl<'a> SomeType<'a> {
    fn worker_func(arg: &'a str) -> &'a str {
        arg
    }
    fn worker_method(&self, arg: &'a str) -> &'a str {
        arg
    }
}

The former is an associated function you would call like

SomeType::work_func(arg)

and the latter is a method that requires an instance of SomeType, which you would typically call like

some_type.worker_method(arg)

There's no obvious reason from the code why either one should take &'a str specifically.


  1. Valid function bodies would include those that never return like panic!() or loop {}, and those that manage to return two valid non-aliasing &mut u8 -- which could be done by leaking memory for example. ↩︎

  2. every function has a unique type, similar to how every closure has a unique type ↩︎

Oops

I didn’t realise that, but would be same result in terms of usability with the create_restrict and restrict
If the same effect, when does covariance and invariance involved and effects the usability

And you said

When do you know borrowing checking conflicts, annotations constraints need proving and when do you the value being conflicted of borrowing

Would associated functions with lifetime differ from associated methods with lifetime, I am still confused on that bit

I mean the first bad func to be

fn bad_func<'a, 'b>(arg: &'a mut u8) -> (&'a mut u8, &'b mut u8)

Not an ‘b unused (accidentally)

What about this

fn abcrefthing<'a, 'b, ‘c>(arg: &'a mut u8, arg2: &’b mut u8) -> (&'c mut u8) where ‘c: ‘a, ‘c: ‘b {
      match false {
            false => arg
            true => arg2
      }
}

What differs from if abcrefthing function is all lifetime parameters are same

I get this

// Combination = “arg: type”

fn no_lifetime(/*...any finite combination*/) -> /*any finite return types*/
// e.g 
// fn example(arg0: u8, arg1: u16, arg2: u128, arg3: StructyType) -> ([u8; 5], u128, StructyType)

I get this

fn once_lifetime< /*’non-static lifetime*/ >(arg: /*struct with lifetime or reference to a type*/, /*any finite combination, or none*/) -> /*struct with lifetime or reference or a type, and if in tuple then (any return type or not with static lifetime)*/
// Single field tuple 
// e.g 
// fn example2<‘a>(life0: &’a mut u8, arg0:  u8) -> (&’a mut u8, )
// Can be elided

I partially get this

fn once_lifetime_returning_2_lifetime< /*’non-static lifetime*/ >(arg: /*struct with lifetime or reference to a type*/, /*any finite combination, or none*/) -> /*if in tuple or array then (any return type or not with static lifetime or non static lifetime)*/ // Must twice includes a lifetime
// e.g 
// fn example2<‘a>(life0: &’a mut [u8; 2], arg0: u8) -> [&’a mut u8; 2]

Would you use unsafe and know wouldn’t cause undefined behaviour, for example splitting borrows and when returning types with lifetime, is it always same to smaller amount (unless internal functions used unsafe like .split_at_mut()) Im still little confused.
e.g

fn retet<‘a>(arg0: &’a u8, arg1: &’a u16) -> &’a u8
//               2 input     1 output

I get this very little

struct SomeType

impl<‘a> SomeType {
      fn a(arg: &’a u8) -> &’a u8 {
             unimplemented!()
      }
}

Why is it unergonomic, of course, the niche knowledge was provided, but I more clarification please.
Does it act same if you implemented like

fn a<‘a>(arg: &’a u8) -> &’a u8 {
      unimplemented!()
}

And interact the same when putting bounds into lifetimes with lifetimes defining in structs and in impl<‘a, ‘b, ‘etc> like

struct SomeType

impl<‘a, ‘c> SomeType {
      fn a<‘b>(arg: &’a u8, arggy: &’b u8) -> &’c u8 where ‘b: ‘c, ‘a: ‘c {
             // Of course Im not good at lifetimes so I might bound it wrong
             unimplemented!()
      }
}

is same as this?

fn a<‘a, ‘b, ‘c>(arg: &’a u8, arggy: &’b u8) -> &c u8 where ‘b: ‘c, ‘a: ‘c {
      unimplemented!()
}

Except only differs in type level and trait level implementation?
Or does it act differently (in practice) as differences in the type and trait level matter
I just want little more clarification
And how does these bounds work and the examples provided, when does lifetime “extends” the type and when do you know that like with &’a &’inner u8 example provided above when deferring the type and causes a compiler error
I have forgotten few semi colons in the “struct SomeType”

Also I don’t get this bounds as much

T: ‘a

Of course T monomorphised into a concrete type like for example this work

Option<u8>: ‘static

I know structs with static lifetime references or non is an static lifetime, I get that.
But what does this T: ‘a mean?
And why this work

struct A {
     a: for<‘a> fn(&’a u32) -> &’a u32,
}

What differs from this way

struct A<'a> {
     a: fn(&'a u32) -> &'a u32,
}

Sorry for my incompetence in lifetimes
And should I learn temporal logic for understanding lifetimes
I like to disambiguate my own understanding and lack of my understanding (in lifetimes) as well

Also I seen a technique of this

struct A<‘a> {
    a: core::marker::PhantomData<&’a ()>
}

Most often it’s used for unsafe and using lifetime to statically avoid ub is what I assume,
How does a person know what signature to use in associated methods and functions and the signature of the function when using with unsafe pointers internally.
Also does it matter if its like this

struct A<‘a> {
    a: core::marker::PhantomData<&’a mut ()>
}

Or is lifetime variance same and only be affected with reference referring to references
like

struct A<‘a> {
    a: core::marker::PhantomData<&’a &’/*b or a*/ ()>
}

And does it matter when using same lifetime or different lifetime when put in pratice

Is lifetimes interdependent labels or independent labels or dependent labels?

I'm trying to pinpoint what confuses you. Is it how you can go from one exclusive reference to two?

The function with a concrete signature can be implemented safely:

fn example2<'a>(life0: &'a mut [u8; 2], arg0: u8) -> [&'a mut u8; 2] {
    match life0 {
        [zero, one] => [zero, one]
    }
}

This is like using field projection to reborrow multiple fields of a struct:

struct S {
    i: i32,
    s: String,
}

fn example2<'a>(life0: &'a mut S) -> (&'a mut i32, &'a mut String) {
    (&mut life0.i, &mut life0.s)
}

The compiler understands how to "split borrows" so that the reborrows of life0.i and life0.s don't conflict. For arrays and slices, you have to use pattern matching to accomplish this instead of indexing, which is why I used a match above instead. Aside from that nuance, it's the same idea.

Reborrows are powerful in that you can discard the original &mut _ while returning reborrows through the &mut _, which is what the two examples do. It's also what things like Vec::get_mut do in their method bodies. It's underdocumented but occurs a lot; Rust wouldn't be really usable without it.

It's generally really hard to get the unsafe right with lifetimes and with references, which have a lot of guarantees. But it can be done sometimes. That said, I wouldn't recommend using unsafe to get around borrow checker errors[1] generally, like transmuting reference lifetimes say. It's almost always unsound.[2][3]

If you're changing the lifetimes (to a "smaller amount"?), which cases are sound or not depends on the particulars. Sometimes a lifetime in a type is covariant (can freely shrink but not grow), sometimes it's invariant (cannot be changed), and occasionally it's even contravariant (can freely grow but not shrink).

I was surprised it compiled to be honest, since the lifetime isn't on the struct.

Anyway, it does act the same as

fn a<‘a>(arg: &’a u8) -> &’a u8 {
      unimplemented!()
}

Those act the same way it almost all ways, including how borrow checking works (using the return value keeps the borrows of *arg and *arggy active). I believe the only difference is that SomeType::a has one lifetime parameter and a has three. Which only matters if you turbofish the lifetime... which you typically don't do (outside of examples / experiments).[4]

    let local = 9;
    SomeType::a::<'static>(&local, &0);
    a::<'_, 'static, '_>(&local, &9);

Unsure what the question is here, or maybe the question is too general.

T: 'a means that for all lifetimes 'x_n which appear in the concrete type, 'x_n: 'a must hold. In practical terms for values, it means that if T holds onto any references or reference-like types, they're valid for at least 'a. In practical terms for types, it means that T is a valid type and usable for at least everywhere that 'a is valid.

We say that for<'a> fn(&'a u32) -> &'a u32 is a "higher-ranked type". The function pointer can take and return a &'a u32 for any lifetime 'a. In contrast, the fn(&'a u32) -> &'a u32 in the A<'a> can only take and return &'a u32 for a single particular lifetime.

Here's a related example which will hopefully help explain the difference. There's a lot of supporting code unimportant to the main point for this example, so I'll walk through it here too:

struct WeirdStruct<'a> { ... }
impl<'a> WeirdStruct<'a> {
    fn weird_func(&self, arg: &'a u32) -> &'a u32 { ... }
}

The important part is that you can only call some_weird_struct.weird_func(arg) when arg has exactly the lifetime 'a that appears in the type of weird_struct.

struct A1 {
     a: Box<dyn for<'b> Fn(&'b u32) -> &'b u32>,
}

struct A2<'x> {
    a: Box<dyn 'x + Fn(&'x u32) -> &'x u32>,
}

These take the place of your fn(..) types, they are just more general to make the example easier (to allow using capturing closures and not just function pointers).

    let ws = WeirdStruct::new(&9);
    // let __ = A2 { a: Box::new(move |arg| ws.weird_func(arg)) };
    // let __ = A1 { a: Box::new(move |arg| ws.weird_func(arg)) };

You can construct A2 because there is a single lifetime for A2<'x> that can satisfy all the requirements. It will be same as the lifetime in the type of ws, because that's the only lifetime that will work with the closure that calls ws.weird_func.

You cannot construct A1 because the type of a in A1 requires a closure that can take an arg with any lifetime 'b. But ws.weird_func can only take one specific lifetime.[5]

(For function pointers specifically, fn(&'x u32) -> &'x u32 for a specific lifetime 'x is pretty odd, because function pointers can't capture state like closures can.)

It's a broad question. And "how to write correct unsafe" deserves a book, not some forum comments. But generally you use PhantomData<T> with a T that has the correct variance and auto-traits for your use case.

One case you might do this is when writing a &mut _-returning iterator for your collection, when &mut _ just has too many guarantees for how your implementation works (but your implementation is still sound). In this scenario you may fall back to using raw pointers but store a PhantomData<&'a mut TheData> to represent the discarded &'a mut _ you used to create the iterator, in a way the type system understands. For, yes, being sound (avoiding UB).

Variance applies to all parameters, types and lifetimes.[6] Every parameter has a variance in the context of the overall type. For example, 'a is covariant in &'a mut T and T is invariant in &'a mut T. The reference has some more details and examples.

Note that &mut T and &T may implement different auto-traits.


  1. outside of experimenting (i.e. not in deployed code) ↩︎

  2. There are some scenarios where you may need to learn certain unsafe patterns off the bat, like if you need FFI to integrate with C or something. Probably you'd want to find a dedicated guide in that case. ↩︎

  3. If you're curious if a particular piece of unsafe is sound or not and why, you could post the example and ask. ↩︎

  4. I don't think I've ever seen it in "real (production) code". Maybe 'static for "self documenting" purposes. In contrast, turbofishing types is not uncommon. ↩︎

  5. If you could construct A1 (or do so with unsafe) it would be unsound; you could easily store a reference and then make it dangle by calling the closure with an arbitrarily short lifetime. ↩︎

  6. and technically consts I suppose, but consts in the type system are invariant so its moot ↩︎

1 Like

Oops again I keep making mistake,
What I mean on the first on is was meant to be this signature

I wasn’t clear on my behalf what I meant was you, do you know how you can’t do this due to limitations of the current borrow checker, you know better than me, if it is an limitation, or that the point?

fn main() {
    let mut a = [6, 5, 4, 7, 9, 10];
    let b = &mut a[..3];
    let c = &mut a[4..];
    let e = b;
}

So it resolved using split_at_mut() for non aliasing mutable pointers, that what I meant by “split borrows”

The first function was meant to be this

fn aaa<‘a>(arg: &’a mut [u8; 2]) -> (&’a mut [u8; 1], &’a mut [u8; 1]

as this example links to the “split_at_mut()” method but I have another question why does this in

fn aaa<‘a>(arg: &’a mut [u8; 2]) -> (&’a mut [u8], &’a mut [u8]) {
     arg.split_at_mut(1)
}

Is it compiler built in or is it possible without and how does when changing it to <‘a, ‘b>… &’a mut [u8], &’b mut [u8]
It knows that bad, it due to signature of the split_at_mut(1) -> … having same lifetime with 2 mutable pointers also
The lifetime is normal borrow and when using

fn abd<‘a>(&’a self) -> &’a Self {
      self
}

of course this obvious this returns the type ‘a and itself but
Does pattern destruction (SomeStruct.field) and borrowing it return local borrow lifetime and when using a type and accessing what does its not transform into local borrow lifetime

Indexing slices and arrays with literal usizes is a builtin operation, and yet this does not work:

    let mut a = [6, 5, 4, 7, 9, 10];
    let b = &mut a[3];
    let c = &mut a[4];
    let e = b;

It could work if the compiler kept track of the literal indices, but it does not -- as far as I know intentionally, just like the block in a if false { ... } is not considered dead code. It doesn't "want" to work based on values, but more on things like types.

You can fake it with slice patterns in this case.

    let mut a = [6, 5, 4, 7, 9, 10];
    let [_, _, _, b, c, ..] = &mut a;
    let e = b;

But with dynamic values, yeah, you'd use split_at_mut and the like.

Indexing arrays and slices uses the Index and IndexMut traits, which borrow the entire slice or array. So unless/until the language grows some new features, or indexing by ranges becomes a compiler builtin operation, there's no straightforward way the compiler could allow the &mut a[..3] example, unlike the indexing with literal usize case.

Here I think it's more that there's no safe unchecked way to split an exclusive array ref into two non-overlapping smaller array refs (e.g. with something pattern-like). With arbitrary lengths, that is (you could do this for the specific example). In principle there could be (e.g. perhaps you could do it with a fancier match pattern or something).

No, it's ultimately raw pointer arithmetic.

If the indices are known at compile time, yes -- with pattern matching as shown above. Or if things like panics were delayed somehow maybe. But in general I'd just say "no".

You could only soundly return &'b [u8] if 'a: 'b, but it also doesn't gain you anything (the lifetimes are covariant and the caller can perform that coercion if they need to).

If you're not using unsafe, the borrow checker will throw an error on the unsound versions.

With something using unsafe and raw pointers, like split_at_mut_unchecked, you could compile an unsound version. When you use an unsafe { ... } code block, it is up to you to uphold all the language requirements instead of the compiler -- you told the compiler "hold my beer" "I got this".

Do you mean why you can do something like

// Same thing:
// abd<'a>(self: &'a Self) -> &'a String {
fn abd<'a>(&'a self) -> &'a String {
    &self.field
    // Same thing
    // &((*self.).field)
}

and how?

If so, that's because you can reborrow fields through references. Reborrowing will keep the original borrow alive. But it's not borrowing the reference itself -- self -- the &'a Self. There is no &'local &'a Self here. Borrow checking ensures that *self remains borrowed while the reborrow is in use. But the original reference -- the &'a Self -- can go away (because it is not borrowed itself).

The return value is a not a borrow of a local, it's a reborrow of (*self).field -- something that exists outside of the function, reachable by an existing borrow (self: &'a Self) via dereferences and field access.