Preventing too short-lived input parameter


#1

Hi,

I’m trying to setup lifetimes such as that the user can’t send in some data that doesn’t outlive the handler in this case.

This code bellow works as intended but I have some questions regarding it (see under the code)

//#![feature(nll)]

use std::marker::PhantomData;
use std::os::raw::c_void;
use std::mem::transmute;

struct Handler<'a> {
    _marker: PhantomData<&'a u32>,
}

impl<'a> Handler<'a> {
    pub fn on_change<T>(&mut self, data: &'a mut T)
    where T: 'a,
    {
        unsafe {
            let user_data: *const c_void = transmute(data);
            println!("{:?}", user_data)
        }
    }
}

fn main() {
    //let mut foo = 1; // this line should be valid
    let mut handler = Handler { _marker: PhantomData };

    let mut foo = 1; // this line should be illegal
    handler.on_change(&mut foo);
}


(Playground)

1:


if I change:

pub fn on_change<T>(&mut self, data: &'a mut T)

to

pub fn on_change<T>(&self, data: &'a mut T)

It doesn’t prevent the local variable to be sent in. Why is that?

2:


Also if I enable NLL on nightly it also doesn’t work (user can send in the variable without any compilation error) Wondering if my code is wrong or that this is an issue with NLL?

3:


I also would like to support doing

handler.on_change(&mut foo);
handler.on_change(&mut foo);

But the compiler will complain that it’s already borrowed. I figured it would only be borrowed when sent to the function and then be allowed to be re-borrowed when it comes back (adding scopes around it doesn’t help either)

Thanks!


#2

This is a mystery to me.

BTW, I was able to remove unsafe from the code: https://play.rust-lang.org/?gist=a0db812455e7cf57d3755dbb02351d9b since pointer casts are allowed in safe Rust (and preferred to transmute).


#3

This is what I came up with.

  1. Need to change Handler to be invariant over its lifetime parameter. This prevents &self from accepting a shorter lived foo.
  2. Need to add a dummy Drop impl to create a “strictly outlives” relationship for 'a.
  3. To allow for your #3, you cannot borrow foo mutably for 'a because the compiler will want to borrow it beyond the lifetime of handler, which essentially gives you a chance to call on_change just once for it.

In general, lifetimes aren’t really meant to enforce strict outlives relationships like you’re looking for here. My attempt sort of does it but I don’t think it’s all that pretty. Maybe there is a better way that someone can think of.


#4

Thanks! Yeah that is to be preferred (this code is a snippet from some code that had FFI taking in a *const c_void and while testing I was using that)


#5

Thanks! :slight_smile:

You have some great points there that I didn’t think about. I will try your solution for now but if someone else has a better one I would interested in that as well.