rustscopelifetimetransmute

Transmuting non-static mutable reference into static mutable reference inside a scope within the lifetime


Following code (playgroung) 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: Although my question was about the change of lifetime, the example is a bit artificial since it can be rewritten without transmute, as Chayim mentions, simply by defining fn f1(x: &mut f64), fn f2(x: &mut f64) and fn f3(x: & mut f64), instead of fn f1(x: &'static mut f64), fn f2(x: &'static mut f64) and fn f3(x: &'static mut f64).


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.