I think both "as" and using "transmute" for non-exceptional circumstances are mistakes in Rust. There should instead be a bunch of type-specific cast operators that can check things like alignment and that what you intended to be a zero-extending integer cast is not in fact truncating to a smaller integer type, and so on. It's not too late to deprecate "as" and discourage using "transmute" in favor of those.
Unsafe Zig Is Safer Than Unsafe Rust
61–70 of 105 posts
Re: Unsafe Zig Is Safer Than Unsafe Rust
#62Rust is a language that offers you lots of compile time checks, and an escape hatch called unsafe that says “trust the programmer here.” Yes, it is possible—and easy—to make mistakes in the place where you have asked to be trusted, not checked. We have a big pedagogical task ahead of us in teaching safe practices for unsafe Rust, and defensive coding practices in unsafe Rust. We should also think of if we can improve…
C++ largely suffers from the same problems. Often a C++ programmer can write code which relies on iterators and containers which is quite safe and difficult to mess up, while for a variety of highly-specialized applications, mixtures of packed structs, pointer arithmetic, and arbitary sequences of binary data need to be handled with utmost care. Knowing when to use which set of tools and how to safely glue them toget…
C++ has improved quite a bit, from my perspective, anyway.[1] That said, I'm excited about Rust and have started (shallowly) exploring it. I like what I see, so far; particularly with improvements on the ergonomics of the language. Seeing it put to use in major projects (cough Firefox) successfully and reading about the problems it solved for Mozilla is the main reason I've set a goal to become proficient in it this year. It's a tall order to commit to a new language, particularly when the other languages I write in generally do everything I need them to. There's a small number of things, though, that still pull me toward C++, and I'd rather have an alternative.
As pleasantly surprised as I was with C++, I had plenty of four-letter-word-riddled moments. Practically all of it stemmed from old libraries, or legacy pieces/parts with my favorite being "lets look at the documentation to see what kind of string this method expects/returns". Character encoding, character byte-sizes, differences between byte-length and semantic length are all complexities when dealing with strings -- many of which get hidden away by CLRs or JVMs or script interpreters. And I'm sure there's some reasons that a person with moderate C++ knowledge could tell me as to why so many of the recently developed (proprietary) libraries seemed to love to pass pointers to non-unicode character arrays around (performance? comfort? nationalist? satan worship?), but it was a punch in the face when I knew an "easy" std::string was right there and never needed to be a character array/serve as a buffer/do anything but be a unicode string for a brief moment of existence. And if I have to figure out why Hunter failed to download the boost library because someone statically linked it to cURL without https support, or used the built-in implementation and compiled it with the wrong flags, or for whatever reason, the downloaded version fails the SHA1 check Every. Single. Time. ... well, no need to conclude that one.
Heck, I'd argue crates is a C++ killing feature for me. Yes, Hunter can be made to work (kicking and screaming, sometimes) with cmake, which I'm told can also be made to work. Microsoft has one, too (I can't remember its name and I know they were working on making it possible to just "use NuGet"[3], but I've always felt that a lack of easy dependency retrieval and management caused three problems (1) people use old libraries that are very likely to be present on the target build host, (2) people write their own (poor, naive) implementations for Solved Problems(tm) or (3) the miserable fck doesn't build, there's not enough documentation to figure out in blue-blazes is, who wrote it and where it came from and when you do* finally find it, it won't build because it's missing its dependencies, so pick (1) or (2) or give up. Compared against '(package-manager) install (package)' and hey, I'm writing code like I originally set out to!
Wow, this devolved pretty quickly into a rant. My apologies for that -- it really isn't as bad as I've made it sound and I realize that most/all of these are my problems and I'm not knocking a language (or folks who program in it) for not bending to my will and having every feature I want, but I'm hopeful for what's coming around with Rust, D and others that are tackling the systems programming space. This Zig article caused me to read several others, as well. The compile-time variables as a workaround for lack-of-macros[3] looks like an interesting idea -- I'm not sure if the syntax is clear enough (globals are implicitly compile-time) but since it's a somewhat unfamiliar syntax, I lack experience to speak intelligently on that.
[0] My adventure started with troubleshooting a very consistent memory leak that was generally caused by some code-in-a-loop that failed to delete things. Often the solution was to change code to use something from boost (which it took a hard dependency on, anyway) or wrapping it in a class and RAIIing my way to a better reality. (and can we get a new acronym? I always write RIAA and if I don't write it, I see it and hairs on my neck stand up)
[1] I "gave up" C++ development around 2001 and short of reading code on rare occasion, didn't seriously start working in the language again until a couple of years ago. I felt like I was writing in a different language -- not sure if that was perception having been away from it for so long, or if it really was that different -- it took a lot of reading to get to a point where I was comfortable breathing in the direction of the code I was playing with.
[2] And NuGet could be a good option, here, especially if they move away from its roots of being somewhat of "it's really just a powershell script with a kludgy metadata file" since I'd rather not add yet another shell to my non-Windows hosts that already have alternatives that I prefer. Last I looked -- .Net Standard pre-2.0, they were fixing the metadata problems -- and maybe they weren't all that bad to begin with considering I can't think of the last time NuGet got in my way on a .Net app.
[3] Though, ideally, I want both.
edit: fix some bad footnote pointers - sheesh, can't even write a comment without a segfault
Re: Unsafe Zig Is Safer Than Unsafe Rust
#63 #[derive(Copy, Clone, Debug)]
#[repr(C)]
struct Foo {
a: i32,
b: i32,
}
fn main() {
let mut array = [Foo { a: 0x01010101i32, b: 0x01010101i32 }; 256];
let foo = &mut array[0];
foo.a += 1;
}
The unsafe section isn't even required, and the effect is the same. And I don't think this violates the spirit of his example, either. Consider the author's first link to a real-world occurrence of this: let size = mem::size_of::();
let mut name_info_bytes = vec![0u8; size + MAX_PATH];
let res = GetFileInformationByHandleEx(handle,
FileNameInfo,
&mut *name_info_bytes as *mut _ as *mut c_void,
name_info_bytes.len() as u32);
This is again, IMO, the wrong way to do this. You should just cast a pointer to an instance of the FILE_NAME_INFO struct into a c_void; the structure will need to use #[repr(C)] and the code will still be unsafe due to the C FFI, but it will be correct (and a lot simpler). This is the same thing that you would do in C, were you to call this function: FILE_NAME_INFO file_name_info;
GetFileInformationByHandleEx(
handle,
FileNameInfo,
&file_name_info,
sizeof(file_name_info),
)
just in Rust.Re: Unsafe Zig Is Safer Than Unsafe Rust
#64Earlier quoted context omitted.
C++ largely suffers from the same problems. Often a C++ programmer can write code which relies on iterators and containers which is quite safe and difficult to mess up, while for a variety of highly-specialized applications, mixtures of packed structs, pointer arithmetic, and arbitary sequences of binary data need to be handled with utmost care. Knowing when to use which set of tools and how to safely glue them toget…
Container and iterator code is not safe at all since there is no bounds checking by default and no protection against iterator invalidation, which can both cause writes to memory outside the intended object and thus a catastrophic outcome. There is no safe subset of C/C++ unless you just don't use pointers or references at all (and refrain from using any library that is not safe which includes large parts of the stan…
Rust, CLR/JVM/interpreted languages are 'safe' because the compiler will flat out refuse to do things that are unsafe (with exception to Rust and some non-interpreted languages allowing you to declare portions of code with as 'unsafe'/'hold my beer'). Short of bugs in compiler/standard library, or unsafe code from libraries written in 'unsafe' languages that are consumed by safe languages (which usually requires a bug in the library, not a bug with how the library is called in the "safe" context, but not always), C++ is 'not safe at all' by comparison. I think if you swap the word 'safe', with 'reliable', that was what the individual you were replying to was getting at. 'Safe' in this context is: "The compiler put the foot-shooting-gun in a safe", vs. 'reliable' is "the gun is in my hand, has no safety, and a somewhat light trigger but it's aimed at the target, not my foot ... as far as I know".
You can handle pointers and references safely as well as use components of the standard library that don't do bounds (or a lot of other, "perfectly reasonable but missing for performance/philosophical reasons") checks, but it's up to you.
A really terrible analogy: it's illegal to drive a car where I live with either of the front passengers lacking a safety belt. Heck, you can't even build a car without a number of safety features that regulation requires. It's also got a number of features to help you avoid accidents. If you or someone screws up on the road, you're protected by the safety features and your mastering of driving. That's the 'safe' programming languages that most people use these days. C++/C is like my motorcycle. The only safety features it comes with rely entirely on my skill at not only "not making mistakes" but anticipating the mistakes of others -- I've had several close calls but have been able to maneuver around other distracted drivers/library maintainers, but if I'm not paying attention to everyone/everything around me I'm toast. And even then, some accidents are unavoidable that would have been survivable with a steel cage and a safety-belt[0].
[0] But damn, that bike is fast, and unlike C/C++, it's a lot more fun to use than the safer alternatives.
Re: Unsafe Zig Is Safer Than Unsafe Rust
#65Earlier quoted context omitted.
The problem with talking about this subject is that "safe" and "unsafe" are overloaded terms in Rust, so I can understand why you think I was talking about something different. Let R be arbitrary Rust code with no "unsafe" blocks. Let X and Y be libraries with "unsafe" blocks. You can prove that R + X is safe, and prove that R + Y is safe, but you haven't yet proven R + X + Y is safe. This is the hard part, because w…
You have restated your position, but it is still incorrect in the context of this discussion. Even your original statement of "R be[ing] arbitrary Rust code with no 'unsafe' blocks" is problematic: any Rust code is, very unavoidably, built upon a foundation of unsafe code. It has to be , because it's running on an "unsafe" processor. And yet, any safe Rust code in the core library (barring a safeness bug) is obviousl…
And what are those safety guarantees? This is the part where I see a lot of handwaving.
> ...either R + X + Y is safe or one of [X, Y] has a safety bug and is inaccurately marking an unsafe interface as safe.
Correct, but the problem is that we don't have a way to identify which library is incorrect without a definition for what a "safe interface" is. If R + X were unsafe or R + Y were unsafe we would have an easy answer to that question.
> This is a generally unsolvable problem...
The fact that the problem is unsolvable in general did not stop people from inventing the Rust language in the first place. The point of Rust is to solve this problem for a larger and more useful class of programs. Likewise, the research into defining what a "safe interface" is in Rust is important and useful research, e.g., RustBelt.
On a minor note, these kind of negative interactions with individual Rust community members have given me a bad impression of the Rust community as a whole.
Re: Unsafe Zig Is Safer Than Unsafe Rust
#66The x86 ABI enforces alignment of the stack to 16 bytes. Isn't that enough to make this particular problem go away?
No. Nothing guarantees that the array is aligned within the stack frame, even if the stack frame is aligned. What if the compiler introduced a boolean flag (for instance, a drop flag) immediately before the array, in the same stack frame?
[0] https://blogs.msdn.microsoft.com/oldnewthing/20140627-00/?p=... - worth a read for some entertainment - basically what happens when the compiler assumes "undefined behavior" can't happen and optimizes accordingly.
Re: Unsafe Zig Is Safer Than Unsafe Rust
#67Earlier quoted context omitted.
p. 66:3, the two paragraphs starting with "However, there is cause for concern..."
Thanks! I'm not convinced that the statement in the paper translates into what you said: the key piece of that paragraph is "or seems to be". The Leakpocalypse problem was one piece of code (crossbeam's scoped threads API) was relying on an invariant that doesn't actually hold ("destructors will always run"). It was, fundamentally, a bug in the `unsafe` code in crossbeam, meaning it was incorrect for crossbeam to cal…
To "observe unsafe behavior" means I can write a program that does something safe, e.g., a data race or invalid memory access. It's possible to write library X and Y in such a way that I can observe unsafe behavior using both X and Y in my program, without putting "unsafe" blocks in my program. This is possible even if I can't do the same thing with either X or Y alone.
This is surprising, because it means that the naive definition of "safe interface" is not actually safe enough!
Re: Unsafe Zig Is Safer Than Unsafe Rust
#68Rust is a language that offers you lots of compile time checks, and an escape hatch called unsafe that says “trust the programmer here.” Yes, it is possible—and easy—to make mistakes in the place where you have asked to be trusted, not checked. We have a big pedagogical task ahead of us in teaching safe practices for unsafe Rust, and defensive coding practices in unsafe Rust. We should also think of if we can improve…
There's not only a pedagogical task here, but the Rust community must learn how to write code safely. The major difficulty here is that in general, unsafe pieces of code cannot be safely composed, even if the unsafe pieces of code are individually safe. This allows you to bypass runtime safety checks without unsafe code just by composing "safe" modules that internally use unsafe code in their implementation. This kin…
This is wrong. GND plus TypeFamilies or some other extension in that vein used to be unsound when combined. It has since been fixed via the introduction of type roles.
> Composed atomic operations are not atomic.
Incidentally, Haskell also has this figured out via the STM monad.
Re: Unsafe Zig Is Safer Than Unsafe Rust
#69Earlier quoted context omitted.
That's what it means if you use it properly. If you write bad code, it means "this code will break everything and the compiler won't protect you." An `unsafe` block does nothing to guarantee that you're doing something safe, which is what you seem to be saying, even if it's not what you mean to say.
This is pretty much tautological, and nobody's arguing this point. However, you have the benefit of being able to narrow your search scope to the parts of your code marked `unsafe` instead of the entire project. Most things don't need unsafe code. For the things that do, you must yourself uphold the invariant that all requirements of safety are being obeyed when transitioning out of an unsafe block. If you don't do t…
I may be missing some context, but this is certainly not true in Rust. In order to understand whether an individual piece of code marked `unsafe` is actually correct, you need to examine the context in which it is run and in general you could have to examine a large section of "safe" code in order to figure out whether the "unsafe" block is correct. Usually you will have to examine the entire module.
Re: Unsafe Zig Is Safer Than Unsafe Rust
#70Earlier quoted context omitted.
There's not only a pedagogical task here, but the Rust community must learn how to write code safely. The major difficulty here is that in general, unsafe pieces of code cannot be safely composed, even if the unsafe pieces of code are individually safe. This allows you to bypass runtime safety checks without unsafe code just by composing "safe" modules that internally use unsafe code in their implementation. This kin…
> see GeneralizedNewtypeDeriving, which is considered unsafe even though it used to be safe This is wrong. GND plus TypeFamilies or some other extension in that vein used to be unsound when combined. It has since been fixed via the introduction of type roles. > Composed atomic operations are not atomic. Incidentally, Haskell also has this figured out via the STM monad.