Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this use of unsafe trivially safe?

I'm running into a Rust borrow checker error that I believe is a limitation of the current implementation of non-lexical lifetimes. The code I want to write looks something like this:

struct Thing {
    value: i32
}

impl Thing {
    fn value(&self) -> &i32 {
        &self.value
    }
    fn increment(&mut self) {
        self.value += 1;
    }
}

/// Increments the value of `thing` if it is odd, and returns a reference to the value.
fn increment_if_odd(thing: &mut Thing) -> &i32 {
    let ref_to_value = thing.value();
    if (*ref_to_value % 2) == 0 {
        return ref_to_value;
    }
    thing.increment();  // fails to compile because the immutable borrow `ref_to_value` is still alive
    thing.value()
}

Rust Playground.

First question: am I right in thinking that this code is 100% safe and the borrow checker is being too conservative? The branch that returns ref_to_value doesn't mutate thing so the reference is guaranteed to be valid, and the other branch doesn't use ref_to_value at all. (I understand that if I replace return ref_to_value; with return thing.value(); it will compile, but in my actual code the value method is expensive.)

It seems I can fix this by "laundering" the reference through a pointer:

if (*ref_to_value % 2) == 0 {
    return unsafe {
        &*(ref_to_value as *const i32)
    }
}

Second question: is this trivially safe? I've never used unsafe before so I'm nervous.

I guess a third question: is there a way to rewrite this in safe Rust? The constaint is that value should only be called once on the non-mutating path.

like image 247
James Fennell Avatar asked Oct 22 '21 16:10

James Fennell


People also ask

What does unsafe mean in Rust?

The different meanings of unsafe unsafe fn : calling this function means abiding by a contract the compiler cannot enforce. unsafe trait : implementing the trait means abiding by a contract the compiler cannot enforce.

How do you use unsafe in Rust?

To switch to unsafe Rust, use the unsafe keyword and then start a new block that holds the unsafe code. You can take five actions in unsafe Rust that you can't in safe Rust, which we call unsafe superpowers. Those superpowers include the ability to: Dereference a raw pointer.

What do you mean by unsafe conditions?

Any condition or situation (electrical, chemical, biological, physical, mechanical and environmental) which increases the risks and dangers of accidents can be called as unsafe conditions. Now, with the same bike accident example, we shall derive the reasons for “unsafe conditions”.

What are some examples of unsafe work conditions?

Similarly, there can be several examples for unsafe work conditions. Non availability of safety gadgets, equipment or protective wears. Hence, we can conclude that unsafe acts are related to the way people handle their tasks. It can be avoided by equipping workers with adequate safety knowledge and skills to manage workplace safety and hazards.

What is the meaning of unsafe act?

Unsafe Act can be defined as any activity by workers which are not as per the prescribed safety standard or practice and which can cause or likely to cause accidents or risk for self or others at workplace, damage equipments and bring losses in terms of reputations and revenue to employer.


Video Answer


1 Answers

The reason the compiler won't allow your code is because ref_to_value must have a lifetime at least as long as the lifetime of the increment_if_odd in order to be returned.

If you add back in the elided lifetimes, ref_to_value must have lifetime 'a. And it's my understanding that the compiler can't change the lifetime of a reference. One way to write safe rust to get around this is to make ref_to_value mutable, and modify Thing::increment.

What your unsafe code does is allow the compiler to give ref_to_value a shorter lifetime, and the new reference, created by casting the pointer, lifetime 'a. I think your unsafe code is "safe" because none of rust's borrowing rules are broken, and we know that the new reference won't outlive the data.

struct Thing {
    value: i32
}

impl Thing {
    fn value(&self) -> &i32 {
        &self.value
    }
    fn mut_value(&mut self) -> &mut i32{
        &mut self.value
    }
    fn increment(val: &mut i32) {
        *val += 1;
    }
}

/// Increments the value of `thing` if it is odd, and returns a reference to the value.
fn increment_if_odd<'a>(thing: &'a mut Thing) -> &'a i32 {
    
    let ref_to_value : &'a mut i32 = thing.mut_value();
    if (*ref_to_value % 2) != 0 {
        Thing::increment(ref_to_value);
    }
    ref_to_value
}
like image 54
CalebA. Avatar answered Oct 19 '22 13:10

CalebA.