|
|
Subscribe / Log in / New account

Key Rust concepts for the kernel

By Jonathan Corbet
September 17, 2021

Kangrejos
The first day of the online Kangrejos conference was focused on introducing the effort to bring the Rust programming language into the Linux kernel. On the second day, conference organizer Miguel Ojeda shifted to presenting the Rust language itself with an emphasis on what Rust can provide for kernel development. The result was a useful resource for anybody who is curious about this project, but who has not yet had the time to become familiar with Rust.

Ojeda began by stressing that the talk was not meant to be a tutorial; to actually learn the language, one should get a good book and work from that. There is no way to cover everything needed in an hour of talk (a fact that became abundantly clear as time went on), but he hoped to be able to show some of the key ideas behind Rust. In the end, though, the only way to really understand the language is to sit down and write some code.

Reading material

There are a number of resources available for developers wanting to learn Rust, including several books. The definitive book appears to be The Rust Programming Language, which is available online for free. The book is a good introduction, he said, and is also an example of the documentation style that pervades the Rust project. Developers who are specifically interested in kernel development need not read the whole thing; much of the discussion on concurrency, for example, is not applicable to the kernel environment.

Programming Rust is also quite nice, he said. It is aimed at experienced programmers, and thus may be useful for kernel developers. This book is the only resource on his list that is not freely available, though. For readers who want to jump right in, Rust by Example presents a series of exercises, each of which introduces a new language feature.

Then, there is The Rustonomicon. This book, which is not complete, focuses on the challenges of writing unsafe Rust in particular. If the Rust-for-Linux project's goals are met, driver writers will not need to write unsafe Rust and, thus, may not need this book. There is a lot of gritty, low-level material in this book, but it also demonstrates a key point: most Rust developers should never have to deal with the language at this level.

Finally, The Rust Reference, which is also incomplete, is a good book for "people who like to read language standards". Most Rust developers will not need it. Ojeda summarized this section by suggesting either of the The Rust Programming Language or Programming Rust for most developers.

The Rust project offers a lot of other documentation too, of course. For the C language, Ojeda said, the standard is the documentation, but Rust is different. Documentation is everywhere, full of explanations and examples, and often generated directly from the code. A similar approach is being taken to the documentation generated by the kernel effort; it is being written using rustdoc rather than the kernel's Sphinx-based system. How to integrate those two bodies of documentation remains an open question.

[Compiler
Explorer]

Tools

Ojeda moved on to a couple of tools that are useful for looking at Rust code and the machine code that it is compiled to. One of those is the Rust playground, which can build code, run linters, examine assembly code, and more.

That said, he prefers Compiler Explorer; it offers a lot of the same features but is not limited to Rust. It can be used to compare assembly output produced by different languages, for example, and features a nice basic-block display for that purpose. Compiler Explorer can also run the code in question and display the results.

The language

While some people describe Rust as "a safer C", Ojeda doesn't like that term. It's more like a version of C with a safer type system, which is not the same as being safety-critical. Rust can also be described as a "cleaned-up C", he said. As an example, he presented a snippet of similar code in three languages:

    map(v, [](const auto & x) { return x.get(); });   // C++
    map(v, lambda x: x.get())    # Python
    map(v, |x| x.get());  // Rust

The Rust version is just cleaner and easier, he said. C++ may contain a lot of useful features, but developers often fear to use them; Rust makes it all easier.

He spent some time on the concept of "safety" in Rust, a discussion that repeated a fair amount from the first day. Rust is "safe" in that it has no undefined behavior; there are no situations where "the compiler has the freedom to do crazy things". Many undesirable behaviors, including kernel panics, memory leaks, and integer overflows, are well defined and, thus, Rust-safe (the compiler can be made to check for overflows though). And, of course, logic errors are "safe"; the language cannot guarantee that a program does what the developer intended.

But many other types of problems can be eliminated by coding in safe Rust. As an example of undefined behavior, Ojeda showed a simple C function:

    int f(int a, int b) {
        return a/b;
    }

There are two ways that this function can wander into undefined behavior. The obvious one is if the caller passes zero for b; if that happens, the compiler is allowed to produce any result it likes. The other undefined situation comes about if a is INT_MIN and b is -1. There is no positive equivalent of the largest negative integer in the two's complement representation, so the result of the division cannot be represented. Once again, the compiler is allowed to improvise when that happens.

One can write a similar function in Rust:

    pub fn f(a: i32, b: i32) -> i32 {
        a/b
    }

Using Compiler Explorer, Ojeda showed that this function, when compiled, contains tests for both of the above undefined-behavior cases. If either is encountered, the program will abort. That may not be what the programmer wanted, but neither is continuing to run in an undefined state.

There was some brief discussion of the performance cost for all of these checks. It certainly is not zero, but nobody seems to have measured what the impact really is in a performance-critical situation. Ojeda pointed out that, in the worst case, code can be put into an unsafe block, which will remove all of those checks. "Unsafe" was often described as an "escape hatch" during this conference — a way to remove many of the constraints placed by the language when they become too heavy.

There are also various tricks that can be applied to show the compiler that certain situations are impossible, at which point it will omit the checks automatically. If one defines a bounded-integer type, for example, the compiler knows that its value will be within its bounds and may not need to perform overflow checks. This technique often involves putting the necessary checks into a constructor, where they only need to be done once.

Ojeda provided one other example, being C code in a function that looks like this:

    int x;  /* not initialized */

    while (f())
        x = 42;
    return x;

Looking at the assembly output for this function, he pointed out that the compiled code just returns a constant 42. The loop body that assigns to x may never have executed, but to reference x in that case puts the program into undefined behavior. The compiler is allowed to assume that this will never happen; in that worldview, x must be assigned the value 42. Rust, instead, will throw a compile-time error in this case and force an explicit initialization of the variable.

Laurent Pinchart noted that developers must train their brains to avoid undefined behavior when working in either language. The difference is that the Rust compiler will catch mistakes, while a C compiler will often remain silent. After training your brain with a language like Rust, he said, the result will often be writing better C code as well.

The time for the session had long since run out by this point, but Ojeda pressed through a series of additional slides describing other Rust language features that are relevant to kernel developers. These include union types that don't allow access to the wrong member, implicit freeing of dynamically allocated objects, handling signed integer overflow, mechanisms for avoiding data races, and more. There is, in short, a lot in Rust for systems developers — much more than can be covered in a brief conference session.

Index entries for this article
KernelDevelopment tools/Rust
ConferenceKangrejos/2021


to post comments

Key Rust concepts for the kernel

Posted Sep 18, 2021 0:23 UTC (Sat) by roc (subscriber, #30627) [Link] (11 responses)

> Ojeda pointed out that, in the worst case, code can be put into an unsafe block, which will remove all of those checks.

Not sure if they have been correctly quoted, but to be clear, "unsafe { a/b }" does *not* remove the division-by-zero check or the overflow check. It is a common misconception that "unsafe removes all checks". In fact almost all checks still apply. "unsafe" enables five specific "superpowers", and that's all it does:
https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#u...

Key Rust concepts for the kernel

Posted Sep 18, 2021 7:14 UTC (Sat) by matthias (subscriber, #94967) [Link] (4 responses)

Indeed, to get rid of the division-by-zero check you have to use unchecked_div(a,b), which obviously requires unsafe. But this is nightly and should therefore not be used in the kernel.

You can get rid of the overflow check by using wrapping division. This even works without unsafe, as INT_MIN/-1 in wrapping semantics is well defined, i.e., INT_MIN.

And I just realize that the article states that -INT_MAX/-1 is undefined. Of course -INT_MAX/-1 is simply INT_MAX.

Key Rust concepts for the kernel

Posted Sep 18, 2021 13:23 UTC (Sat) by PengZheng (subscriber, #108006) [Link]

At first I was confused by the -INT_MAX/-1 statement, then saw your comment.
Thanks for your clarification.

Key Rust concepts for the kernel

Posted Sep 18, 2021 14:02 UTC (Sat) by tialaramex (subscriber, #21167) [Link]

> Of course -INT_MAX/-1 is simply INT_MAX.

I had to stare at this for a moment. Yes, it should say INT_MIN / -1 is undefined. Perhaps our Editor can consult his notes and, if that's what was actually said, correct the article, or else, point out in a parenthetical that it should have been INT_MIN.

Key Rust concepts for the kernel

Posted Sep 18, 2021 14:24 UTC (Sat) by ojeda (subscriber, #143370) [Link]

> But this is nightly and should therefore not be used in the kernel.

Note that we currently use quite a few of unstable features (of course, we want to minimize them as soon as possible).

> You can get rid of the overflow check by using wrapping division.

Indeed. I think having all the wrapping/saturating/... operations in `core` is a nice feature. In related news, C23 is getting checked integer operations (N2683).

> Of course -INT_MAX/-1 is simply INT_MAX.

I told Jonathan about the typo yesterday, but he is likely very busy with LPC coming up.

INT_MAX

Posted Sep 18, 2021 14:27 UTC (Sat) by corbet (editor, #1) [Link]

It looks like somebody (other than me) fixed that error, apologies for the mistake.

Key Rust concepts for the kernel

Posted Sep 18, 2021 14:06 UTC (Sat) by ojeda (subscriber, #143370) [Link] (5 responses)

> Not sure if they have been correctly quoted, but to be clear, "unsafe { a/b }" does *not* remove the division-by-zero check or the overflow check.

I assume "code" here is used in the general sense, not the literal `a / b` expression copy-pasted into an `unsafe` block.

In the actual talk, the assembly generated for different examples was shown, including e.g. a comparison with `unchecked_*()` etc.

> It is a common misconception that "unsafe removes all checks". In fact almost all checks still apply. "unsafe" enables five specific "superpowers", and that's all it does:

What is critical to understand is that `unsafe` gives the burden of the UB-less proof to the user. What rules are or not in place is second-order for maintainers evaluating Rust.

Moreover, saying "almost all checks still apply" is not particularly useful, because one still needs to know all the rules. In fact, I find the statement can be misleading for newcomers -- they may get overconfident thinking the compiler is going to catch "almost all" soundness violations.

Key Rust concepts for the kernel

Posted Sep 20, 2021 0:42 UTC (Mon) by roc (subscriber, #30627) [Link] (4 responses)

I haven't done any teaching in this space and I rarely use "unsafe", but AFAIK the extra review required for "unsafe" code is simply checking that every use of those five superpowers is valid.

Key Rust concepts for the kernel

Posted Sep 20, 2021 0:44 UTC (Mon) by roc (subscriber, #30627) [Link] (2 responses)

Which makes me wonder if it would be a good idea for Rust to support "limited unsafe" blocks that enable a specific superpower. E.g. "unsafe(call) { unsafe_function(); }", "unsafe(deref) { *x = 0; }", "unsafe(mut_global) { global = 100; }" etc

Key Rust concepts for the kernel

Posted Sep 20, 2021 2:20 UTC (Mon) by tialaramex (subscriber, #21167) [Link] (1 responses)

The biggest current trip hazard is that by default an unsafe function's entire body is allowed to be unsafe.

That is, having declared your function unsafe (meaning a caller ought to read your documentation to ensure they use it safely), you are free to do anything unsafe inside it, even if you didn't intend to.

There is a (default off) lint to warn you that this might be a bad idea, and I especially encourage you to use this if you write unsafe functions that are, from Rust's point of view, technically safe (e.g. they actually control important hardware, and so callers must read your documentation to use them safely or bad things might happen, but they will not cause Undefined Behaviour as far as Rust is concerned and so do not technically need Rust's unsafe "super powers"). The reason the default isn't default on is that many common unsafe functions have line 1-2 lines inside them, and those are genuinely unsafe code, so with the lint you'd be adding redundant unsafe blocks inside these tiny unsafe functions.

Key Rust concepts for the kernel

Posted Sep 21, 2021 10:11 UTC (Tue) by ojeda (subscriber, #143370) [Link]

> There is a (default off) lint to warn you that this might be a bad idea

Indeed -- in Rust for Linux we are already using `unsafe_op_in_unsafe_fn` (as an error, in fact, rather than a warning) and I hope it becomes the default in a future Rust edition.

Key Rust concepts for the kernel

Posted Sep 20, 2021 9:49 UTC (Mon) by ojeda (subscriber, #143370) [Link]

> I haven't done any teaching in this space and I rarely use "unsafe", but AFAIK the extra review required for "unsafe" code is simply checking that every use of those five superpowers is valid.

This is correct, except for the "simply" ;)

Being able to prove those uses are sound can range from trivial to very complex.

For instance, it is trivial to know a dereference is valid if you just acquired a valid pointer within the same function. But in other cases the reasoning is non-local -- the "safety boundary" might be the type or the module.

Another example is doing FFI. It requires understanding the safety preconditions of a foreign function, which could be documented or not. If not documented, then unraveling that is likely to require further non-local reasoning. And, of course, all this in a different language.

Another complicating factor is that the `unsafe` rules are not perfectly specified, yet (yes, the "superpowers" are clearly defined, but those are just the "API", so to speak).

Key Rust concepts for the kernel

Posted Sep 18, 2021 13:29 UTC (Sat) by PengZheng (subscriber, #108006) [Link] (13 responses)

int x; /* not initialized */

while (f())
x = 42;
return x;

IIRC, Coverity can catch this kind of error.
If a static analysis tool can, so can a compiler like GCC in the future?

Key Rust concepts for the kernel

Posted Sep 18, 2021 14:42 UTC (Sat) by tialaramex (subscriber, #21167) [Link]

A few problems:

1. Because the language does not outright forbid this construction, these will necessarily be optional diagnostics. C has been around for decades at this point and we can say from experience that (to a first approximation) none of those who most need them will enable the optional diagnostics.

2. Because the language does not outright forbid this _type_ of construction, there may be cases where the programmer believes, correctly or otherwise, that forcing them to eliminate Undefined Behaviour costs performance, which invariably C programmers are loathe to accept. So those cases become reasons not to have the general warning, or, if it exists, not to enable it, particularly on large codebases like Linux.

3. Outright forbidding Undefined Behaviour is safer because it forces the language designers and compiler developers to address any corner cases. Without this you get a mismatch between the set of things the language actually defines, and the set of things diagnosed by your tools as Undefined Behaviour, with some things slipping through the gap between the two.

Key Rust concepts for the kernel

Posted Sep 18, 2021 17:12 UTC (Sat) by flussence (guest, #85566) [Link]

It certainly could do. GCC already has to understand whether a loop is bounded or unbounded for various -ftree-* optimisations, and it annotates variable assignments for SSA opts.

The downside is it needs higher settings than -O0 to understand code at that depth, but that's true of many optimising compilers.

Key Rust concepts for the kernel

Posted Sep 18, 2021 19:17 UTC (Sat) by dave_malcolm (subscriber, #15013) [Link] (2 responses)

FWIW, I've implemented that for GCC 12 (with -fanalyzer): https://godbolt.org/z/Tjojofjzo
warning: use of uninitialized value 'x' [CWE-457] [-Wanalyzer-use-of-uninitialized-value]
    9 |   return x;
      |          ^
  'test': events 1-3
    |
    |    7 |   while (f())
    |      |          ^
    |      |          |
    |      |          (1) following 'false' branch...
    |    8 |     x = 42;
    |    9 |   return x;
    |      |          ~
    |      |          |
    |      |          (2) ...to here
    |      |          (3) use of uninitialized value 'x' here
    |

Key Rust concepts for the kernel

Posted Sep 18, 2021 20:34 UTC (Sat) by ojeda (subscriber, #143370) [Link] (1 responses)

Thanks a lot David for your work -- I think it is invaluable.

The analysis seems to bail out when the address is taken (e.g. https://godbolt.org/z/xE6deTTTG) for some reason, but not in all cases (see second example where it seems to be analyze the body of `g`).

Also, it seems the warning does not appear if optimizations are enabled, is that expected?

Key Rust concepts for the kernel

Posted Sep 19, 2021 21:28 UTC (Sun) by dave_malcolm (subscriber, #15013) [Link]

Thanks for "taking it for a spin" :)

Re your 1st example: for better or worse, the analyzer works on the SSA representation. Taking the address of x means gcc doesn't use ssa names for x, and this changes what the analyzer "sees" (which can be seen with -fdump-ipa-analyzer=stderr)

Re your 2nd example: g has an empty body, so the analyzer "knows" that it doesn't write to x, and that x is still uninitialized. (this can be seen with -fanalyzer-verbosity=4)

Optimizations affecting the output is another side-effect of my choice to use the SSA representation (which gains us some things, but costs us others...) I may need to revisit that choice.

Key Rust concepts for the kernel

Posted Sep 24, 2021 14:27 UTC (Fri) by error27 (subscriber, #8346) [Link] (7 responses)

These days GCC's uninitialized variable warning is disabled in the kernel because apparently the latest version is quite buggy. Nathan Chancellor is using Clang to warn about them. Smatch will also warn about them but normally Nathan is getting to them before I do which is a relief for me because there were a lot of warnings. Also a lot of the bugs were really embarrassing.

You are using a loop in your code. Traditionally, GCC used to assume that the code would enter loops at least once. Smatch would only assume that when it was certain about it. This caused a lot of false positives in Smatch when it's not intelligent enough about the callers. Actually "this function is never called with an empty list so we will always enter the loop". Or "this function will never be called with length zero."

In some cases I view handling size zero as a readability and hardening improvement. But other times it's maybe not clear what is the correct way to handle a condition like that and it's impossible anyway so we may as well leave the code as-is.

Key Rust concepts for the kernel

Posted Sep 24, 2021 19:54 UTC (Fri) by nybble41 (subscriber, #55106) [Link] (6 responses)

> Traditionally, GCC used to assume that the code would enter loops at least once.

If programmers intend that a loop will always be entered at least once they should use a do/while loop, which is defined to have those semantics. The use of a plain while loop implies that the condition may be false on the first iteration. If Smatch was reporting false positives it was due to the code being incorrect, not any issue with Smatch's treatment of while loops.

Key Rust concepts for the kernel

Posted Sep 24, 2021 21:31 UTC (Fri) by Wol (subscriber, #4433) [Link]

OMG. We learnt that with FORTRAN/Fortran back in the seventies!

FORTRAN-IV always did the test at the bottom of the loop so was guaranteed to execute at least once. Fortran-77 did the test at the top of the loop so would skip the loop if the test was false the first time round.

The compiler I used had a switch to apply the FORTRAN logic to Fortran.

Cheers,
Wol

Key Rust concepts for the kernel

Posted Sep 27, 2021 11:51 UTC (Mon) by error27 (subscriber, #8346) [Link] (4 responses)

No... Often times while loops are more readable and that's always the most important thing.

And even if I thought one style was more readable than another, I'm not trying to be the style police. People sometimes suggest adding style rules to Smatch but I don't want any part of that.

The fix is to make the cross function analysis more robust. That ends up paying for itself in other ways as well.

Key Rust concepts for the kernel

Posted Sep 27, 2021 20:38 UTC (Mon) by nybble41 (subscriber, #55106) [Link] (3 responses)

The more readable version is always going to be the one that says what it means: a while loop for zero or more iterations, and a do/while loop for one or more. Requiring the reader to have extra context about how the function is called to determine whether variables are properly initialized before being used is *not* what I would consider "more readable". That's prioritizing a minor style preference above *correctness*.

Key Rust concepts for the kernel

Posted Sep 27, 2021 21:56 UTC (Mon) by Wol (subscriber, #4433) [Link] (2 responses)

Aren't you missing something? Languages where the syntax is

DO ... WHILE ... UNTIL ... END DO

???

What's important (and this is where FORTRAN/Fortran failed), is that the test should be carried out where the test is written.

DO WHILE condition
statements
END DO

or

DO
statements
WHILE condition END DO

Cheers,
Wol

Key Rust concepts for the kernel

Posted Sep 27, 2021 23:13 UTC (Mon) by nybble41 (subscriber, #55106) [Link]

> What's important (and this is where FORTRAN/Fortran failed), is that the test should be carried out where the test is written.

The context is C code compiled by GCC and Clang. C doesn't have any issue with the placement of the test. A while loop puts the test at the top of the loop, and evaluates it at the top of the loop (before the first iteration of the loop body). The do/while loop places the test at the bottom and evaluates it after executing the body of the loop.

However, if you use a while loop in a context where it would be *incorrect* (as in: results in reading from uninitialized variables) to exit the loop before the body had executed at least once, there is a bug in your code, or at least the very strong *potential* for one even if you think you know that the inputs will always cause the body to be executed. In the absence of some clear human- and machine-readable indication of the initial condition, such as assert() statements, the compiler or other static analysis tool should warn about the potentially uninitialized variable(s). Even with assert() statements I would consider it better to use do/while since it better reflects the actual flow control vs. "assert(expr); while (expr) { ... }" which has the false appearance of a test and branch prior to the first iteration which will (hopefully) be removed by the compiler as dead code.

Or you could structure the code so that it reliably does the right thing even if the initial condition results in the body of the loop not being executed. That would be ideal: easier to read and reason about, while handling a wider range of inputs without risking undefined behavior.

Key Rust concepts for the kernel

Posted Sep 27, 2021 23:22 UTC (Mon) by anselm (subscriber, #2796) [Link]

Standard FORTRAN didn't even have “DO WHILE … END DO” loops (although some implementations added them as a non-standard extension). You would emulate structured loops like these with “IF” and “GOTO” .

The other thing was that it was pretty easy to get loops wrong. The moral equivalent to a “FOR” loop in FORTRAN looks like

      DO 10 I=1,10
        …
10    CONTINUE
IIRC the line with the statement label “10” doesn't have to be “CONTINUE” (which is basically a no-op which is useful to hang statement labels off of), although it makes for more readable loops. But you would have to take care not to accidentally write something like
      DO 10 I=1.10
        …
10    CONTINUE
where, since spaces inside variable names are allowed in FORTRAN and variables don't need to be explicitly declared, the compiler would assume the first line to be an assignment statement rather than the beginning of a loop (having a “10 CONTINUE” statement without a corresponding “DO” isn't an error, either). FORTRAN 77 changed the syntax of “DO” to optionally allow
      DO 10, I=1,10
which would prevent the error, and back in a previous life when I was the TA for a FORTRAN course at the university (I was young and needed the money) I would heavily penalise students who didn't avail themselves of this safety measure.

INT_MIN / -1

Posted Sep 19, 2021 1:22 UTC (Sun) by dskoll (subscriber, #1630) [Link] (2 responses)

In some cases, gcc on Linux gives a floating point exception and terminates the program in this case. I compiled the following code:

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int im = INT_MIN;

int divide(int a, int b)
{
    return a/b;
}

int main(int argc, char **argv)
{
    int b = atoi(argv[1]);
    printf("%d\n", divide(im, b));
}
And running it gives:
$ ./test -1
Floating point exception

On the other hand, if you change the first assignment in main to int b = -1 then you get:

$ ./test 
-2147483648

INT_MIN / -1

Posted Sep 19, 2021 17:17 UTC (Sun) by ojeda (subscriber, #143370) [Link]

Yeah, the compiler is free to create a program that raises SIGFPE (or does anything else) if you reach a state that triggers UB. This is what allows the compiler to ignore handling that case.

If, instead, the standard defined that e.g. `abort()` is called, then the compiler would need to ensure that happens somehow (e.g. by guarding the division or by handling hardware exceptions).

INT_MIN / -1

Posted Sep 29, 2021 17:13 UTC (Wed) by anton (subscriber, #25547) [Link]

My guess is: you compiled and ran on AMD64 (or IA-32), and the code as shown actually performs a division, and the processor produces an exception due to the integer overflow in this case, which the OS translates into a SIGFPE (with appropriate si_code if you pass SA_SIGINFO to sigaction). By contrast, in the case where gcc can see that b=-1, it tries to constant-fold the division and botches it (of course, they will claim that you bothed your program).

Key Rust concepts for the kernel

Posted Sep 29, 2021 17:24 UTC (Wed) by anton (subscriber, #25547) [Link]

As it happens, I tried pretty hard to find a performance difference between checking before the division and checking afterwards (my theory was that checking afterwards should run in parallel with the division, while checking before should not). Even with specially crafted microbenchmarks, the differences were small on some CPUs and non-existent on others. Therefore I consider it unlikely that the check has any measurable effect in production code. However, I did not compare a variant without any check, so that's something a sufficiently interested individual can still do (maybe based on my microbenchmarks).

Learning resources

Posted Oct 10, 2021 2:54 UTC (Sun) by yodermk (subscriber, #3803) [Link] (6 responses)

Just to note, I read the free online The Rust Programming Language earlier this year. I am now reading the new edition of Programming Rust. The latter is way, way, WAY better. It covers a lot more things and describes things in more detail. This is one case where it's definitely worth ponying up for the dead tree. That said, the online book will get you started.

Learning resources

Posted Oct 10, 2021 7:50 UTC (Sun) by jem (subscriber, #24231) [Link] (5 responses)

Programming Rust is also available as an e-book, if you want to avoid killing a tree.

Another fresh Rust book is Rust for Rustaceans by Jon Gjengset. This book is for "developers who have mastered the basics." I haven't read it myself yet, but I have been watching some of the author's Crust of Rust videos on YouTube. But be warned: some of the videos are over five hours long.

Learning resources

Posted Oct 10, 2021 15:44 UTC (Sun) by jezuch (subscriber, #52988) [Link] (2 responses)

> But be warned: some of the videos are over five hours long.

For the curious, this podcast explains why: https://rustacean-station.org/episode/038-jon-gjengset/

Learning resources

Posted Oct 12, 2021 7:12 UTC (Tue) by MrWim (subscriber, #47432) [Link] (1 responses)

Hmm, to find out why some YouTube videos are over 5hrs long listen to a 1h20m podcast ;)

Learning resources

Posted Oct 12, 2021 13:31 UTC (Tue) by jezuch (subscriber, #52988) [Link]

Lol :) On the other hand they are listing timestamps so it's easy to find the relevant segment.

Learning resources

Posted Oct 31, 2021 9:11 UTC (Sun) by mcortese (guest, #52099) [Link] (1 responses)

Do you know if it's available as epub, though? I could only find the Kindle version, which I'm not a big fan of.

Learning resources

Posted Oct 31, 2021 11:00 UTC (Sun) by peniblec (subscriber, #111147) [Link]

"Programming Rust" (2nd edition) can be found DRM-free on ebooks.com. As for "Rust for Rustaceans", no starch press seems to provide PDF and ePub versions.


Copyright © 2021, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds