Search code examples
rustlifetimeunsafe

Is it valid to transmute a non-static reference to a static one if its only used within the original lifetime?


The following code (playground) works with miri, but is it free of undefined behavior?

use std::thread;

fn f1(x: &'static mut f64) {
    *x += 1.0;
}

fn f2(x: &'static mut f64) {
    *x *= *x;
}

fn f3(x: &'static mut f64) {
    *x = (*x).log10();
}

fn main() {
    let mut a = vec![1.0f64, 9.0, 100.0];
    let funcs = vec![f1, f2, f3];
    let a_mut = a.iter_mut().collect::<Vec<_>>();
    thread::scope(|s| {
        for (x,f) in a_mut.into_iter().zip(&funcs) {
            s.spawn(|| {
                f(unsafe{std::mem::transmute::<_,&'static mut f64>(x)});
            });
        }
    });
    println!("a -> {a:?}");
}

In this code, a non-static mutable reference is sent to a function taking a static mutable reference as input, using a transmute. However, execution is constrained to a scope within the lifetime of the mutable reference.


Similar question with Fn(&'static mut f64) (playground)

use std::thread;

fn f1(x: &'static mut f64) {
    *x += 1.0;
}

fn f2(x: &'static mut f64) {
    *x *= *x;
}

fn f3(x: &'static mut f64) {
    *x = (*x).log10();
}

fn main() {
    let mut a = vec![1.0f64, 9.0, 100.0];
    let funcs = vec![
        &f1 as &(dyn Fn(&'static mut f64) + Send + Sync),
        &f2 as &(dyn Fn(&'static mut f64) + Send + Sync),
        &f3 as &(dyn Fn(&'static mut f64) + Send + Sync)
    ];
    let a_mut = a.iter_mut().collect::<Vec<_>>();
    thread::scope(|s| {
        for (x,f) in a_mut.into_iter().zip(funcs) {
            s.spawn(|| {
                f(unsafe{std::mem::transmute::<_,&'static mut f64>(x)});
            });
        }
    });
    println!("a -> {a:?}");
}

Note: The example is a bit artificial since transmute can be avoided by not requiring 'static in the first place, but my question was about the change of lifetime.


Solution

  • Lifetimes can never affect behavior. This is not documented in an official place as far as I know, unfortunately, but this is the general conception.

    If you hand those references to unknown code, that would be unsound, but assuming all code is known and doesn't actually use the reference outside of its lifetime, this is fine.