← Back to context

Comment by Ygg2

8 days ago

Unsafe Rust doesn't automagically disable typesystem (& borrow checker, but lifetime are a sort of types).

Once raw pointer is turned into a T, &T or &mut T, the borrow checker is on.

True, but unsafe let's you conjure up any lifetime you want, or any lifetime necessary to satisfy the lifetime requirements in safe code. If you generously sprinkle pointer dereferences in unsafe code, you effectively disable the protection provided by the borrow checker – including in safe code – until you've checked and verified the correctness of all unsafe blocks.

  • > True, but unsafe let's you conjure up any lifetime you want

    The only thing unsafe does is let you have an unbounded lifetime. As I said, it doesn't check those:

       fn get_str<'a>(s: *const String) -> &'a str {
           unsafe { &*s }
       }
    
    

    https://doc.rust-lang.org/nomicon/unbounded-lifetimes.html

    > if you generously sprinkle pointer dereferences in unsafe code, you effectively disable the protection provided by the borrow checker

    You don't disable anything. You wrote a "trust me compiler" block, and compiler trusted you.

    Rust won't ever protect from all possible problems, just the ones the compiler handles.

    • > You don't disable anything. You wrote a "trust me compiler" block, and compiler trusted you.

      That’s the same thing.

    • > The only thing unsafe does is let you have an unbounded lifetime.

      No, you're wrong: You can create any lifetime. Proof:

          fn oof<'desired>(x: &u32) -> &'desired u32 {
              let ptr = x as *const u32;
              unsafe { &*ptr }
          }
      

      This will take a reference and return it with any lifetime specified by the caller.

      > You don't disable anything.

      I said "effectively disable". For example:

      fn trust_me_bro<'a>(x: mut u32) -> &'a mut u32 { unsafe { &mut x } }

          fn main() {
              let mut x = 1_u32;
              let reference_a = &mut x;
              let reference_b = trust_me_bro(reference_a);
              *reference_b = 2;  -- Whoops
              println!("reference_a: {reference_a}  reference_b: {reference_b}");
          }
      

      After the call to trust_me_bro, two aliasing, mutable references exist simultaneously. This would usually be prevented by the borrow checker, but the unsafe code has effectively disabled it.

      3 replies →

Yeah but if you have tried writing unsafe Rust, you notice that you are obligating yourself to write code that is much stricter than C.

E.g. in C you can write code and say "don't call it outside the situation that this function was written for" and then blame [0] future users of the API.

In Rust you need to actually make sure that your code works, i.e. your safe interface is not unsafe.

[0] The blame game is not a technical solution to a technical problem...

Borrow-checking the dereference of a stale pointer won't be worth much, though.

  • Sure; but I bet raw pointers are used very infrequently in the new codebase. The code was ported from zig to rust. I bet a lot of pointers became rust references in the process.

    • The point of compiler-based checking would be that you know for sure. If you have to bet, all bets are off.

    • That’s my whole point! It’s ported to unsafe rust, not default rust.