5 ms·
> This is simply incorrect. If it is possible to, in any safe code, invoke the invariants that can cause the undefined behavior then your code is unsound. UnSaf
by LegionMammal978 4y ago
> This is simply incorrect. If it is possible to, in any safe code, invoke the invariants that can cause the undefined behavior then your code is unsound. UnSafe code _cannot_ rely on safe code invoking it correctly. ALL invariants must be covered at the boundary between safe and unsafe code, anything else is a soundness bug in the code.
That's roughly what I'm saying? The point is that you have to carefully design your interface so that safe code from the outside is unable to break your invariants, no matter how it invokes your interface.
> This is again completely incorrect. If it is possible to invoke unsound behavior from safe code then the code is fundamentally unsound. This is the problem that the Actix developer kept trying to justify that got him in a lot of hot water with the community. Please never write code this way. If you have any public repositories please mention them so bugs can be filed against them.
If you have a public safe function that can cause UB, then it is indeed unsound. But it is possible to have a private safe function that can only possibly be accessed from a small part of your code base. If you are careful in preserving this invariant within the parts of the code that can directly access the function, then creating a sound interface without direct access to the function is entirely possible. (But this obviously gets more risky, the more code that has access to the function. I'll grant that it's not an uncommon pitfall to assume a crate-wide invariant somewhere that you later forget about.)
- codeflo 4y agoYou even have this phenomenon inside single functions. Let's say you write a function that's supposed to be a safe/sound abstraction. You might be setting up a pointer, maybe determined by a dozen lines of codes, that is then finally dereferenced. You'd typically only put the unsafe block around this final pointer access, but its safety depends on the whole calculation being correct.
- zozbot234 4y agoUnsafe blocks are very different from unsafe-marked functions. A function that can indirectly cause UB when called in safe code must be marked as unsafe, so that the invariants it relies on can be properly documented in a Safety: comment, and manually checked by callers.
- LegionMammal978 4y agoOnly if it's public, and you don't know who the callers are. If you do know exactly who the callers are (since it's a private function), and you have internal documentation that lets contributors know of the proper invariants, then it's entirely possible to prevent UB from ever occurring even if the function is marked as safe. By itself, this is usually not a good idea. But in some cases, it's necessary, e.g., an unsafe implementation of an external safe trait on an internal type that only your internal code will ever see.
- zozbot234 4y agoWhy? If you have a non-public function and all its potential callers are known, what's the harm in marking it as unsafe and documenting its invariants in the idiomatic way (a Safety comment)? Your case of implementing a safe trait via unsafe code seems like it should be quite rare, and even then the trait implementation should document the assumptions it's relying on as part of an unsafe block. If you neglect to mark other possibly-UB functions as unsafe, this becomes harder to ensure.
- LegionMammal978 4y agoI agree that it's rarely a good idea to do this; it's a red flag at minimum when you see it. I'm just trying to reject the notion that it's categorically unsound to do this. I've personally seen the safe-trait example while reviewing a crate, and there were indeed plenty of comments documenting the assumptions. As long as there's absolutely no way for anyone to cause these assumptions to be broken from the outside, there's no way to cause UB, and it's not really useful to call the interface unsound.
- mlindner 4y agoIt doesn't matter if it's public or not. Every function is "public" to the person modifying the code. The purpose of unsafety/safety is primarily for the developer, not for users of a library.
- mlindner 4y ago
- mlindner 4y ago> But it is possible to have a private safe function that can only possibly be accessed from a small part of your code base. This would also be unsound. Multiple people develop software in a library. Any future developer on your library will automatically assume that any safe Rust function can be called with any parameters and not invoke undefined behavior. That is the default assumption of any Rust developer. If you have such code in your libraries, you should fix them, or mark them as clearly unsafe such that any caller of that function ensures they keep the invariants that are required of that unsafe code that is propagated upward. You're falling back to old ideas from C. > If you are careful in preserving this invariant This is C/C++ mentality. The entire point about Rust is that you do not need to be careful. It's guaranteed and checked by the compiler. Again, if you can invoke undefined behavior by calling ANY safe function with ANY form of input, then the function is unsound. This is simply how Rust assumptions work. > But this obviously gets more risky, the more code that has access to the function. I'll grant that it's not an uncommon pitfall to assume a crate-wide invariant somewhere that you later forget about. I suggest you re-learn how to program Rust, personally. You've picked up some really bad habits that don't line up with general consensus on how to code in Rust.
- Nemo157 4y agoSince unsoundness is a property of an interface, that private safe function would be unsound but could be used as part of the implementation details of a sound public API. It's not something I would do personally, and would be something I'd put down as a black mark against using a library as a dependency, but it's nowhere near as bad as doing the same thing on a public API.
- mlindner 4y agoEvery single function call is using an interface. Your public API is not the only interface. It's an extremely bad habit to pick up as this ruins one of the main purposes of Rust, namely it's ability to allow collaboration and while not causing undefined behavior. Any sort of internal "don't do this or it'll cause undefined behavior" is just falling back to C/C++ land. Some developer later will modify that code later, maybe even the same developer and will forget about the invariants that need to be held if they're not documented in unsafety comments around unsafe code. Anything that relies on code review to catch unsoundness outside of unsafe code is automatically wrong.