Lifetime transmutes and extensions

struct test_struct();

impl test_struct {

/// Lifetime of 'c is at least the lifetime of 'a
fn return_a_str_made_on_stack<'a, 'c: 'a>(&'a self) -> &'c str {
    let my_str = "Hello world!";
    unsafe { std::mem::transmute::<&str, &'c str>(my_str) }
}

fn closure_a<'a>(&'a self) -> &'a str {
    self.return_a_str_made_on_stack()
}

}

fn main() {
    let val = test_struct();
    assert_eq!(val.closure_a(), "Hello world!");
}

One reason why rust is cool is the necessary use of unsafe in order to return an object from the stack. This is a great source of breaking-bugs in C programs, but in rust, having similar bugs is only possible if unsafe code is used. My question relates to how transmute works. Will the compiler recognize that, since &self: 'a, and c: 'a, that the function closure_a will be outlived by the returned string? The code above runs without error or breaking the assertion, but it is possible, i believe, for this not to be the case as the entropy within the registers begins to pick up from other calculations. Might anyone have a good answer as to why - or why not - this code could lead to problems?

Try let my_str: () = "Hello world!"; the result might surprise you.

You also might want to try to remove the transmute.

like this?

struct test_struct();

(doesn't compile)
impl test_struct {
    /// Lifetime of 'c is at least the lifetime of 'a
fn return_a_str_made_on_stack<'a, 'c: 'a>(&'a self) -> &'c str {
let my_str: () = "Hello world!";
my_str
}

fn closure_a<'a>(&'a self) -> &'a str {
    self.return_a_str_made_on_stack()
}
}

fn main() {
let val = test_struct();
assert_eq!(val.closure_a(), "Hello world!");
}

Yes it doesn't compile, the interesting part is the error.

Hmm, it's not jumping out at me. It all seems expected. What are you seeing that I'm not?

error[E0308]: mismatched types
 --> src/main.rs:6:18
  |
6 | let my_str: () = "Hello world!";
  |                  ^^^^^^^^^^^^^^ expected (), found reference
  |
  = note: expected type `()`
             found type `&'static str`

error[E0308]: mismatched types
 --> src/main.rs:7:1
  |
5 | fn return_a_str_made_on_stack<'a, 'c: 'a>(&'a self) -> &'c str {
  |                                                        ------- expected `&'c str` because of return type
6 | let my_str: () = "Hello world!";
7 | my_str
  | ^^^^^^ expected reference, found ()
  |
  = note: expected type `&'c str`
             found type `()`

error: aborting due to 2 previous errors

Alright, let's go the other way (last riddle-like post after that I'll explain clearly).
What lifetime would you put there?

                               ▼
unsafe { std::mem::transmute::<&str, &'c str>(my_str) }

Hmm. I'm just gonna think-out-loud here.

Well, self has 'a in the function return_a_str_made_on_stack, but the calling closure closure_a is what has self with a lifetime of 'a, so 'a would not be the answer. And how about 'c? Well, even less so, because 'c outlives 'a. Answering you question:

The lifetime in the transmute is elided to be equal to the lifetime implied by the closure of return_a_str_made_on_stack. If that closure is 'd, then 'c: 'a, and 'a: 'd. Is this right?

Would it be correct to say that the main() function is what has lifetime 'c? That was my intention originally

I just learned that I don't need unsafe to make this work!!

struct test_struct();

impl test_struct {
    /// Lifetime of 'c is at least the lifetime of 'a
fn return_a_str_made_on_stack<'a, 'c: 'a>(&'a self) -> &'c str {
    let my_str: &'c str = "Hello world!";
    my_str
}

fn closure_a<'a>(&'a self) -> &'a str {
    self.return_a_str_made_on_stack()
}

}

fn main() {
    let val = test_struct();
    assert_eq!(val.closure_a(), "Hello world!");
}

my life is changed

I expected a respond like that, the answer is way simpler: 'static.
When you think about it it's logical, a &str is a reference but it's out of thin air here, there is no String (for example) to borrow from.
The compiler will allocate the str in your binary and the reference will be to that.
This explains why it works: you never extend any lifetime, you go from 'static to 'c.
So to answer you first question, there is no way for this code to go bad.

The book talks about it here.

2 Likes

What about if I want to return a str that is unknown at compile time (e.g., reading a file)?

In that case that's very different, you'll have to read the file into something (a String most likely) and the slice's lifetime will be bounded to it.
You could also make it static with the help of lazy_static or once_cell for example (or Once if you don't want to use crates).

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.