9 ms·
What Is Rust's Unsafe? (2019)
- oxff 5y agoIt is a very cool abstraction that makes lots of things possible, which are (to my understanding) impossible to do in other languages without the explicit safety contract. Like this: https://www.infoq.com/news/2021/11/rudra-rust-safety/ https://www.infoq.com/news/2021/11/rudra-rust-safety/, and I quote: "In C/C++, getting a confirmation from the maintainers whether certain behavior is a bug or an intended behavior is necessary in bug reporting, because there are no clear distinctions between an API misuse and a bug in the API itself. In contrast, Rust's safety rule provides an objective standard to determine whose fault a bug is." People bring up `unsafe` Rust as an argument against the language, but to me it appears to be an argument `for` it.
- likeabbas 5y agoMy only complaint is I wish they didn't name it `unsafe` and instead named it something like `compiler_unverifiable` so that people could more properly understand that we can make safe abstractions around what the compiler can't verify.
- pjmlp 5y agoThe unsafe concept exists since early 60's in systems programming languages. The real issue here is Rust fans trying to make Rust into a type dependent language while abusing unsafe's original purpose.
- dwohnitmok 5y agoI like the word `unsafe`. It's a nice red flag to newbies that "you almost certainly shouldn't use this" and once you have enough experience to use `unsafe` well you'll know its subtleties well enough.
- likeabbas 5y agoI don't think newbies using it would be a problem. There is an issue with people/companies evaluating Rust and stopping when they see `unsafe` without looking much further into it.
- mynameisash 5y agoI just saw a tweet from Esteban Küber[0] the other day that made me rethink a silly thing I did: "I started learning Rust in 2015. I've been using it since 2016. I've been a compiler team member since 2017. I've been paid to write Rust code since 2018. I have needed to use `unsafe` <5 times. That's what why Rust's safety guarantees matter despite the existence of `unsafe`." For me, I was being a bit lazy with loading some config on program startup that would never change, so I used a `static mut` which requires `unsafe` to access. Turns out I was able to figure out a way to pass my data around with an `Arc<T>`. I think either way would have worked, but I figured I should avoid the unsafe approach anyway. [0] https://twitter.com/ekuber/status/1511762429226590215 https://twitter.com/ekuber/status/1511762429226590215
- oconnor663 5y agoYeah if you don't need mutation after initialization is done, Arc<T> is a good option for sharing. Lazy<T> (https://docs.rs/once_cell/latest/once_cell/sync/struct.Lazy.html https://docs.rs/once_cell/latest/once_cell/sync/struct.Lazy....) is also nice, especially if you want to keep treating it like a global. I believe something like Lazy<T> is eventually going to be included in the standard library.
- jamincan 5y agoIt's already on nightly: https://doc.rust-lang.org/std/lazy/struct.Lazy.html https://doc.rust-lang.org/std/lazy/struct.Lazy.html
- shepmaster 5y agoI’ll be even lazier (heh) and just straight up leak memory for true once-set read-only config values. https://github.com/shepmaster/stack-overflow-relay/blob/273a774678f8a1152239d84b818446e0d9f282d3/src/main.rs#L43 https://github.com/shepmaster/stack-overflow-relay/blob/273a...
- kibwen 5y agoIf I were to go back and do it again I'd propose to rename unsafe blocks to something like `promise`, to indicate that the programmer is promising to uphold some invariants (the keyword on function declarations would still be `unsafe`). (Of course the idea of a "promise" means something different in JavaScript land, but I'm not worried about confusion there.)
- ChadNauseam 5y agoWhile we're at it, I also would have preferred `&uniq` over `&mut`.
- selfmodruntime 5y agoAnd from the great Steve Klabnik himself: StrBuf instead of „String“
- gpm 5y agoI still like `trust_me`, maybe a bit unprofessional in some contexts though.
- kevincox 5y agoOr just `unchecked`. It is similarly short but holds a similar meaning. Of course `unchecked_by_the_compiler` would be more accurate.
- kibwen 5y agoThat's inaccurate, because Rust still performs all the usual checks inside of unsafe blocks. The idea that unsafe blocks are "turning off" some aspect of the language is a persistent misconception, so it's best to pick a keyword that doesn't reinforce that.
- nyanpasu64 5y agoShrug. I really don't think unchecked is "wrong" in spirit, since adding an unsafe block makes it trivial to write code which ignores the usual boundary and pointer lifetime checks, which is not very different in practice from turning off checks. Also unsafe code is usually wrong, and deeply difficult to write correctly, much more so than C. Also, I dislike the word "unsafe" since "unsafe code" is easily (mis)interpreted to mean "invalid/UB code", but "invalid/UB code" is officially called "unsound" rather than "unsafe". Unsafe blocks are used to call unsafe functions etc. (they pass information into unchecked operations via arguments), and unsafe trait impls are used by generic/etc. unsafe code like threading primitives (unsafe trait impls pass information into unchecked operations via return values or side effects). unchecked would make a good keyword name for blocks calling unchecked operations, but I'm not so sure about using it for functions or traits.
- afdbcreid 5y agoTechnically, code that contains UB is just UB, and unsound code is code that allows you to trigger UB using safe code only. For example, this is UB: unsafe { std::hint::unreachable_unchecked() }; And this is an unsound code, that may not contain any UB if I call `unsound()` with `false`: fn unsound(trigger: bool) { if trigger { unsafe { std::hint::unreachable_unchecked() }; } }
- codeflo 5y ago
- verdagon 5y agoJon Goodwin's Cone [0] language had a nice idea of calling it "trust", as in, "Compiler, trust me, I know what I'm doing" [0] https://cone.jondgoodwin.com/coneref/reftrust.html https://cone.jondgoodwin.com/coneref/reftrust.html
- bitbckt 5y agoI like the sound of “trusted”.
- MereInterest 5y agoI think the benefit of the name "unsafe" is that it immediately tells newer users that the code inside has deeper magic than they may be comfortable using. Where "trusted" is what the writer attests to the compiler, "unsafe" is what writer warnings to a future reader.
- retrac 5y agoI believe the term "unsafe" in this sense predates Rust. Haskell's GHC has unsafePerformIO [1], which is very similar conceptually. Generally used to implement an algorithm in a way where it can be pure/safe in practice, but where this cannot be proved by the type system. [1] https://stackoverflow.com/questions/10529284/is-there-ever-a-good-reason-to-use-unsafeperformio https://stackoverflow.com/questions/10529284/is-there-ever-a...
- pornel 5y agohold_my_beer { }
- pksebben 5y agoyeet { }
- seba_dos1 5y ago> People bring up `unsafe` Rust as an argument against the language Those people usually don't understand what `unsafe` is.
- afdbcreid 5y agoI especially like the following quote in that context: > The biggest failure in Rust‘s communication strategy has been the inability to explain to non-experts that unsafe abstractions are the point, not a sign of failure. Appeared on TWiR: https://this-week-in-rust.org/blog/2021/10/20/this-week-in-rust-413/ https://this-week-in-rust.org/blog/2021/10/20/this-week-in-r....
- deleted 5y ago[deleted]
- zozbot234 5y agoThis article fails to mention that the true semantics of Rust's `unsafe` subset has been very much up-in-the-air for a long time. Nowadays the `miri` interpreter is supposed to be giving us a workable model for what `unsafe` code is or is not going to trigger UB but many things are still uncertain, sometimes intentionally so as some properties may depend on idiosyncratic workings of the underlying OS, platform and/or hardware. These factors all make it harder to turn what's currently `unsafe` into future features of safe Rust, perhaps guarded by some sort of formalized proof-carrying code.
- deleted 5y ago[deleted]
- kibwen 5y agoProgress towards this is always being made, though it's a long road with much still to determine. I encourage anyone interested in this to join the Zulip channel for Rust's unsafe code guidelines working group: https://rust-lang.zulipchat.com/#narrow/stream/t-lang.2Fwg-unsafe-code-guidelines https://rust-lang.zulipchat.com/#narrow/stream/t-lang.2Fwg-u... Some recent developments: https://gankra.github.io/blah/tower-of-weakenings/ https://gankra.github.io/blah/tower-of-weakenings/
- oconnor663 5y agoI could be wrong, but my impression from posts like this one https://www.ralfj.de/blog/2020/12/14/provenance.html https://www.ralfj.de/blog/2020/12/14/provenance.html is that even C doesn't have a fully coherent model for what happens when we start doing weird things with pointers. Apparently there are LLVM bugs open that ultimately need to be addressed by the standards committee?
- pjmlp 5y agoESPOL, NEWP, Mesa, Cedar, Modula-2, Modula-2+, Modula-3, Oberon, Oberon-2, Component Pascal, Active Oberon, D, C#, F#, VB, Ada, Go, Swift, just a few examples.
- tialaramex 5y ago
- ridiculous_fish 5y agoSometimes unsafe is used differently, for example when setting up a signal handler or a post-fork handler. Process::pre_exec is unsafe to indicate that some safe code may crash or produce UB within this function. Only async-signal safe functions should be used and Rust does not model that in its type system.
- verdagon 5y agoInterestingly enough, unsafe is the root reason Rust couldn't add Vale's Seamless Concurrency feature [0] which is basically a way to add a "parallel" loop that can access any existing data, without refactoring it or any existing code. If Rust didn't have unsafe, it could have that feature. Instead, the Rust compiler assumes a lot of data is !Sync, such as anything that might indirectly contain a trait object (unless explicitly given + Sync) which might contain a Cell or RefCell, both of which are only possible because of unsafe. Without those, without unsafe, shared borrow references would be true immutable borrow references, and Sync would go away, and we could have Seamless Concurrency in Rust. I often wonder what else would emerge in an unsafe-less Rust! Still, Given Rust's priorities (low level development) and the borrow checker's occasional need for workarounds, and the sheer usefulness of shared mutability, it was a wise decision for Rust to include unsafe. [0] https://verdagon.dev/blog/seamless-fearless-structured-concurrency https://verdagon.dev/blog/seamless-fearless-structured-concu...
- zozbot234 5y ago> Without those, without unsafe, shared borrow references would be true immutable borrow Rust devs have thought about implementing "true immutable" before and found it to be problematic. It would come in quite handy for mostly anything related to FP or referential transparency/purity, but these things turn out to be very hard to reconcile with the "systems" orientation of Rust. Perhaps the answer will reside in some expanded notion of "equality" of values and objects, which might allow for trivial variation while verifying that the code you write respects the same notion of "equality".
- celeritascelery 5y agoRust without unsafe would just be another obscure academic language like Haskell. The ability to bypass the type system when the programmer needs to is what makes Rust work.
- whateveracct 5y agoHaskell also allows you to bypass the type system plenty
- tialaramex 5y agoCrucially, unsafe is also about the social contract. Rust's compiler can't tell whether you wrote a safety rationale adequately explaining why this use of unsafe was appropriate, only other members of the community can decide that. Rust's compiler doesn't prefer an equally fast safe way to do a thing over the unsafe way, but the community does. You could imagine a language with exactly the same technical features but a different community, where unsafe use is rife and fewer of the benefits accrue. One use of "unsafe" that was not mentioned by Nora but is important for the embedded community in particular is the use of "unsafe" to flag things which from the point of view of Rust itself are fine, but are dangerous enough to be worth having a human programmer directed away from them unless they know what they're doing. From Rust's point of view, "HellPortal::unchecked_open()" is safe, it's thread-safe, it's memory safe... but it will summon demons that could destroy mankind if the warding field isn't up, so, that might need an "unsafe" marker and we can write a checked version which verifies that the warding is up and the stand-by wizard is available to close the portal before we actually open it, the checked one will be safe.
- furyofantares 5y agoWould you be able to provide a real example of that HellPortal thing? I'm not really following
- seba_dos1 5y agoI'm not convinced that it's a great use of Rust's `unsafe`, but since you want an example... dealing with voltage regulators maybe? Where an invalid value put into some register could fry your hardware? There's a ton of such cases in embedded.
- oconnor663 5y agoYou could probably make the case that any function that might physically destroy your memory is memory unsafe :)
- mikepurvis 5y agoSimilar to the sibling, stuff where you're dealing with parallel state in hardware, like talking to a device over i2c or something, where you know certain things are supposed to happen but you don't, like, know know.
- EE84M3i 5y agoAs mentioned in the article, it's entirely possible to create a raw pointer to an object in safe rust, you just can't do much with it. One thing you can do with it though is convert it to an integer and reveal a randomized base address, which isn't possible in some other languages without their "unsafe" features. Of course, this follows naturally from Rust's definition of what is safe, but I remember being kind of surprised by it when I first learned Rust and didn't understand those definitions yet. It would be pretty interesting to me if someone wrote a survey of what different languages consider to be "unsafe", including specific operations like this. For example, it looks like "sizeof" is relegated to the "unsafe" package in Go, which strikes me as strange.[1] I'd love to read a big comparison. [1]: https://pkg.go.dev/unsafe#Sizeof https://pkg.go.dev/unsafe#Sizeof
- afdbcreid 5y agoYou can also compare & hash it. I once used it, to store visited objects in a hashset without the borrow checker making me sad.
- afdbcreid 5y agoI am by no means a Go expert, but I think the `unsafe` package is the only standard library package in Go that does not conform to the stability & portability guarantees and `Sizeof` is neither portable nor stable.
- EE84M3i 5y agoAt the top of the page it says "Package unsafe contains operations that step around the type safety of Go programs." though
- staticassertion 5y ago> In a certain sense, this means that everything written in Python is memory unsafe. CPython is also less aggressive about adopting mitigation strategies compared to Rust, although it has improved a lot in the last few years.
- captainmuon 5y ago> Safety, in Rust, is very well-defined; we think about it a lot. In essence, safe Rust programs cannot: > - Dereference a pointer that does not point to the type the compiler thinks it points to. [...] > - Cause there to be either multiple mutable references or both mutable and immutable references to the same data at the same time. [...] Sometimes, I wish there was a systems language that let you exactly do that. Dereference any memory location and see what memory is there. Use a cast to reinterpret the bits. If the memory address is unmapped, then maybe just throw an exception. Allow multiple threads to fill in different spots in a huge array at the same time. Basically write C as high level assembler, like you did in the 90s. I know -fno-strict-aliasing and some other flags can get you mostly there, but you are still violating the spec and it is at best tolerated. I also know that you loose some optimization potential, but it also reduces UB and that is a trade off you should be able to choose.
- Dylan16807 5y agoYou're effectively making every variable volatile if you do that, and you lose nearly all optimization potential.
- qcumberqr 5y agoProgramming is inherently unsafe. Rust only solves a fraction of memory safety, and there are more unsafe things beyond memory concerns. It’s a complete misnomer. “Safety, in Rust, is very well-defined; we think about it a lot.” You can’t just construct some elaborate static analysis complex and call your language “safe” as a result, that’s a surefire way to make your language impenetrable to anyone outside of your field of expertise.