Trait lifetime hack

Hello everyone. Help needed :slight_smile:
I have a trait from foreign crate with method:

fn on_url(&mut self, url: &[u8]) -> bool

I want to implement this trait to my struct, so i can store url slice inside of it without copying.
But, rust complains that lifetime of url slice must outlive lifetime of my stuct.
Limitations:

  1. trait is from another crate, so i can't modify it.
  2. no clone :slight_smile:

That function signature only allows you to look at the URL, and the caller is allowed to destroy it as soon as the call has returnedβ€” If you want to keep it, you have to either make your own copy or use a different API.

Fortunately, URLs tend to be relatively small so making a copy is usually not going to cause performance issues.

2 Likes

a slice is borrowed data, and in rust, a "borrow" has a lifetime attached, and must be backed by some form of owned data.

if you want to "store" borrowed data, you must use explicit lifetime annotations so the compiler can check the code obeys the rules (e.g. the borrow is not dangling, etc).

if you are not comfortable or don't want to bother with explicit lifetime markers, just make a clone and store owned values. slices have a method called .to_vec(), which gives you an owned copy of the slice data.

2 Likes

The "hack" that allows keeping such slices in other languages is the garbage collector.

Rust doesn't have a garbage collector, so it doesn't have a way to keep anything alive for longer than it's been guaranteed via existing lifetime annotations.

In this case you get a default (elided) lifetime that is valid only for the duration of the call, and no longer.

It can't be done in safe code, and overriding that with unsafe can actually crash the program or create a vulnerability, depending on how the function is called.

2 Likes

The lifetime elision rules on this produce the following non-elided signature:

fn on_url<'s, 'u>(&'s mut self, url: &'u [u8]) -> bool

Since there is no constraint between 's and 'u, 'u is allowed to be shorter than 's - or, in other words, the thing that url refers to might be destroyed while self (the referrent of &self) still exists.

I want to implement this trait to my struct, so i can store url slice inside of it without copying.

Then you must guarantee that 'u outlives 's. That requires you to explicitly annotate the bounds:

fn on_url<'s, 'u>(&'s mut self, url: &'u [u8]) -> bool
where 'u: 's

Because of the way variance rules work, you can also write

fn on_url<'s>(&'s mut self, url: &'s [u8]) -> bool

if you prefer.

Either of those would require modifying the trait. Since you can't modify the trait you're implementing, that leaves you with the option of copying the value.

The OP signature says the url lifetime is arbitrarily short, with no relation to the implementing type, and that's enough to conclude you can't soundly store it -- it can be shorter than any borrow that's part of your type.

Beyond that one can discuss what signature would be necessary to enable storage. But I'm afraid your analysis of the lifetimes isn't accurate.


The lifetime on &mut self is how long to have exclusive access to self. You generally don't need -- or want! -- that to be as long as the borrows you store. It can be arbitrarily short, generally speaking. You just need long enough to store something (write the reference to self).

What you would need for soundness is for the lifetime on the &[u8] to be at least as long as the borrow you store -- some lifetime parameter of the Self type. There's no direct way to write this with a fresh lifetime on the function (e.g. some hypothetical where 'u: Self).[1] So generally it would be a parameter of the trait instead.

When things are destroyed is also not really relevant.

If you had 'u: 's, 'u could still be shorter than the borrow you store:

Here's what a parameter on the trait may look like:


  1. This would not always be desired anyway as it would constrain every parameter of the Self type, presumably. β†©οΈŽ

3 Likes

You can (but shouldn't) abuse a compiler bug:

And if the first line was not clear enough, this is entirely undefined behaviour.
Don't use this in production code!

Augh, you're correct, of course. Thank you.

Don't use this in any code.

Imho it's fine to use this in snippets to study the language.
OP asked for a hack to circumvent a language feature. I'd argue that this is a (pretty ugly) hack to do this. :wink:

2 Likes