What is the safest way to fake a reference by temporarily pretending it's `'static`?

Viewed 179

I'm in a not-so-great situation and have to fake a lifetime. It looks a little bit like this:

struct Bar<'a> {
    cr: &'a mut char,
}

fn foo<D, F>(data: D, f: F) 
where
    D: 'static,     // <-- !!!
    F: FnOnce(D),
{ ... }


let mut c = '⚠';
let bar = Bar { cr: &mut c };
foo(???, |c| /* I need access to a `Bar` here! */); 

I have to call the strange function foo. In the closure I pass to it, I need to get access to a Bar (with any lifetime) that was passed through foo. (I know in this minimal example I could just access the bar directly as closures have access to their environment, but let's pretend that's not possible here.) Unfortunately, foo requires D: 'static.

Of course, in reality everything is more complicated. I know Bar and foo don't make too much sense, but this is my attempt at breaking my problem into a minimal example.


How do I make this work? I believe it is possible to make this work safely (i.e. without undefined behavior), but I'm sure it requires the unsafe keyword.

The basic idea is to cast the Bar<'not_static> to a Bar<'static> temporarily, then make sure that it does not outlive the original c. I want to know how to best do that. My idea was the following:

let mut c = '⚠';
let bar = Bar { cr: &mut c };
let bar_static: Arc<Bar<'static>> = unsafe {
    let extended = mem::transmute::<Bar<'_>, Bar<'static>>(bar);
    Arc::new(extended)
};

foo(bar_static.clone(), |bar| println!("{}", bar.cr));

if Arc::strong_count(&bar_static) != 1 && Arc::weak_count(&bar_static) != 0 {
    eprintln!("bad!");
    std::process::abort();
}

The idea is to dynamically check that no references to c exist anymore (apart from the one we are holding) after calling foo. That should protect against foo storing the data in a static variable or something like that. I don't expect it, but I rather end the whole process instead of having memory unsafety in my program.

With this, I think, I make sure that the reference (which is incorrectly 'static) does not outlive the actual data. But:

  • Is that reasoning sane? Does it make sense?
  • What worries me is that rustc doesn't think c is borrowed after the unsafe block. I could (in my function) arbitrarily access c, although there exists a reference pointing to it. Is that a case of "I'm fine as long as I don't actually access c"? Or rather one of those "immediate UB" cases?
  • Is mem::transmute the right tool for the job or should I use pointer casts or something else?
  • Any better ideas?
1 Answers

The answer is that there is no safe way to alter the lifetime of your objects. Lifetimes are compiler guarantees that the value will always live for the duration of its use.

The safest route would be to change foo to include the lifetime of data: D.

fn foo<'a, D, F>(data: D, f: F)
where D: 'a,
      F: FnOnce(D),
{
    f(data);
}

An alternative is to alter Bar and how it's used via Arc<Mutex<char>> and downgrading it to Weak<Mutex<char>>.

struct Bar {
    c: Weak<Mutex<char>>,
}

let t = 't';
let t = Arc::new(Mutex::new(t));

let bar = Bar { c: Arc::downgrade(&t) };

foo(bar, |b| {
    if let Some(c) = b.c.upgrade() {
        let mut c = c.lock().unwrap();
        println!("{}", *c);
        *c = 'b';
    }

});
println!("{}", t.lock().unwrap());

However, if it's not possible for you to change foo or Bar and you are certain that the referenced object will not be dropped, then you can use std::mem::transmute to alter the lifetime of your object.

As mentioned in the doc:

transmute is incredibly unsafe. There are a vast number of ways to cause undefined behavior with this function. transmute should be the absolute last resort.

unsafe fn to_static<'a>(r: Bar<'a>) -> Bar<'static> {
    std::mem::transmute::<Bar<'a>, Bar<'static>>(r)
}


let mut t = 't';
let bar = Bar { c: &mut t };

let static_bar = unsafe { to_static(bar) };

foo(static_bar, |b: Bar| {
    println!("{}", b.c);
    *b.c = 'b';
});

println!("{}", t);

The example above works because t lives within the scope where foo is called and we know t is static so it's perfectly fine to use. However, if foo sends static_bar into another thread, and t is not static, then the result is undefined behaviour.

If you can guarantee that foo will complete before the scope it's called in ends, then you can use Mutex's to do the following.

let t = 't';
let t = Arc::new(Mutex::new(t));

{ // create scope here

    let mut tlock = t.lock().unwrap(); // MutexGuard<char>
    let t_ref_mut = &mut *tlock;       // get &mut char  -- lifetime of mutex guard
    let bar = Bar { c: t_ref_mut };
    
    let static_bar = unsafe { to_static(bar) };
    
    // foo must be guaranteed to complete before the scope ends.

    foo(static_bar, |b| {
        println!("{}", b.c);
        *b.c = 'b';
    });

} // MutexGuard goes out of scope and lock should be released

// sanity check that lock is released and value is altered
println!("{}", t.lock().unwrap());

Full source code can be viewed here

Related