Learn Rust the Dangerous Way - the unsafe-first tutorial

"safe", "sound" whatever. Nobody outside of mathematics knows if that is some technically defined meaning or just casual mathematician slang.

For the same reason can we avoid words like "monad", "monoid", "functor" when talking about Rust?

At the end of the day the source code is mechanically checked for memory aliasing problems or it is not. If not a human has to check it.

So "unsafe" means not mechanically checked by the compiler. If I understand Rust correctly.

That does not mean it is actually unsafe. It means you have to trust a human rather than the compiler.

4 Likes

It's that specific difference that we're trying to capture in a safe/sound distinction. Even if casual usage of the terms is casual, it definitely helps if written material consistently uses two different terms for the different meanings.

Even pure synonyms are noticed when used for disjoint (but related) concepts consistently. If we save even one moment of thinking between "is this "unsafe as in human-checked" or "unsafe as in causes UB", then the term split is worth it. It doesn't even matter if it's a conscious acknowledgement in the reader if we can bias them towards assuming the correct case from the start.

I understand the position of "terminology doesn't matter that much, context will figure it out," but I don't agree with it. (Maybe it's my formal background coming through.) Terminology exists to help the reader understand faster, and we should especially try to make understanding easier around unsafe (as in human-checked) code.

That's why "jargon" isn't great: you either know the meaning or you don't, and if you don't, it's worse than a more ambiguous but more obvious term.

Oh, and one more thing:

That's not correct. Code in an unsafe block is just as checked as code not in an unsafe block. It "just" gives you the superpowers of 1) dereferencing raw pointers, 2) using union fields, and 3) calling other unsafe APIs. This gives you the power to break rules upheld mechanically in not-unsafe code, but nothing changes about the safe subset of the language.

6 Likes

I feel that having a distinction between "this requires an unsafe block" and "this is undefined behaviour" is quite important, and unsafe vs unsound has served this purpose quite nicely for me.

10 Likes

I'm not a native English speaker, and "sound" was part of my non-technical vocabulary meaning exactly what you want to mean by it. So I think its non-technical meaning is specific enough to use it in Rust tutorials trying to avoid technical jargon from math and logic.

7 Likes

Me neither: "folklore" or "pathological" are examples of mathematical jargon, without value judgment :slight_smile:


Update 2019-12-24. Hoping to clarify a bit: "corner case" is jargon, "abstract class" is a term.

Unsoundness is what happens when unsafety goes wrong.

7 Likes

I think everyone here is making this more complicated than it has to be. Undefined behavior (UB) can be expressed simply by stating that code behaves undefined and defined behavior can simply be referred to as code behaving defined. There's no reason to say unsafe (that means you're taking a risk, not that it always goes wrong) or unsound (a term I never hear except when talking about compilers).

Safe code always behaves defined, even in an unsafe environment. Unsafe code behaves defined, as long as its constraints aren't violated, otherwise it behaves undefined.

Often I will use the phrases "undefined behaviour" and "not undefined behaviour", because I feel that the word "defined" is awkward, because it clashes with a different use, namely the behaviour defined by the documentation of the library.

1 Like

That's where the terms specified and unspecified behavior come in.

I think the thing we've learned here is that terminology is hard and often vague, as people prefer different terms, but we shouldn't derail discussion of this (awesome!) resource further to bikeshed over terminology.

9 Likes

Thanks!

I'm going to...

  1. Continue not assuming that my readers have a maths background.
  2. Try to be consistent about using "unsound" to mean "unsafe and wrong" (which I have not been thus far).

While I've mentioned "undefined behavior" in a couple places, I haven't been using it as a bogey-man to discourage broken code, because C programmers are often used to relying on technically undefined behavior to get their jobs done. (Like the behavior of a shift greater than the number of bits in the type, or signed integer overflow, or casting a function pointer to void*.) Not that I'm suggesting that UB is okay; just that "that's UB" has gotten me a shrug instead of a scared-face in the past.

5 Likes

All writing has to have an audience in mind.

Your opening post speaks of "high-performance software community" and C programmers. My experience is that such C programmers have a pretty good maths background.

My difficulty, as a long time procedural language programmer coming to the Rust world, is that the docs assume one comes from some functional programming world, like Haskell or F#. As do many of the conversations in this forum. Totally alien.

1 Like

I'd like to disagree about them expecting you to come from functional programming.

I came from a half-baked self-taught C# background where I didn't understand almost anything about memory.

My personal experience moving to Rust has been wonderful; even from an "essentially heavy interest" to a proficiency with the language, syntax, and general style, I'd argue that the docs and examples for Rust are simply geared towards a general programmer without anyone in mind, as long as they understand programming.

And even then... If the tendency with rust is to use functional programming in some places, would it not make sense to go and learn the paradigm rather than be confused while reading the language? If I went to an F# forum and asked how to write an imperative loop, I'd probably be told a functional style is better in F#.

There’s also heavy overlap with Ruby’s Enumerable module and Rust’s Iterator trait.

At least, that’s where I first became familiar with functional-ish combinators in a reasonably mainstream language. Apart from the usual computer science focus on lisp and scheme in college I didn’t have much exposure to such things.

That is indeed the plan.

But it's not easy. When your whole life has been sequence, selection, iteration expressed as expressions, 'if', 'for' etc, in many different languages, one does not immediately think of 'iter', 'map', 'enumerate', 'fold', 'collect', 'zip' and so on.

These things have not existed in my world. They are not what our computers do down at the machine instruction level. They are costly abstractions that have no place in systems that need to be small, performant and portable.

Rust of course changes all that by bringing them to the realm of compiled systems programming languages. And hence, here I am trying to get acclimatized.

Funny you should mention F#. That is a new kid on the language block. Not any use in my world. But, whilst watching a presentation on functional programming the other day I started to realize where many things in Rust were coming from.

3 Likes

Interesting, I didn't think I had done that -- what are you referring to?

Or are you talking about some docs unrelated to this thread?

No, no, sorry if I was unclear. Your article certainly does not do that.

I was generally referring to that Rust docs, what I read about Rust around the net and discussions I have tried to follow here.

Actually my question was perhaps the opposite, why the need to kick-start C programmers into Rust with "unsafe"?

As a long time C user I was quickly sold on Rust meeting C like performance after a few experiments without ever needing "unsafe".

1 Like

Thanks for this post, was really interesting and I didn't expect the performance to be faster.
According to github I'm using rust since 2015, but I've never tried to use raw pointers this way :sweat_smile:

Great tutorial, I enjoyed it! One thing I noticed:

In a couple places, you transmute from [MaybeUninit<T>; N] to [T; N], using two stack variables where the C code only uses one. Seems like you should either transmute a reference instead, or inform the reader that you're actually making copy which will probably be elided by the compiler.

1 Like

Personally I have always found it better style to mark any function that can exhibit UB as unsafe, even if it's private. Otherwise, you run the risk of introducing UB in the future when you forgot the function isn't actually safe.

1 Like