News for hackers. Stuff that matters.
Re:Hobby x86 kernel written with Zig by jackhalford on Monday January 06, 2020 @07:40AM (#21968661)
In general you'll find that zig is easier to read than Rust (see the first version of this project in Rust [0]) because it's a simpler language. For kernel programming this is even more so the case:

* zig has native support for arbitrary sized integers. In Rust I used to do bitshifts, Now I just have a packed struct of u3/u5/u7 whatever (see `src/pci/pci.zig`). Of course Rust has a bitflags crate but I didn't find it handy, this is a case where native support vs library support means a world of difference.

* zig has native support for freestanding target. I used to have to build with Xargo for cross compiling to a custom target, I also was forced to `[nostd]`, and some features I was using forced me to use nightly Rust. In zig I have a simple `build.zig` and a simple `linker.ld` script, just works.

* zig has nicer pointer handling. A lot of kernel programming is stuff that Rust considers unsafe anyway. It's not uncommon to have to write lines like `unsafe { (ptr as const u8) }` to deref a pointer in Rust, which is a pain because this kind of thing happens all of the time. Also you have to play around with mut a lot like this: `unsafe { &mut (address as mut _)`. It just felt wrong a lot of the time, where in zig you have either `const` or `var` and that's the end of it.

* zig is really fun to write! this is something that comes up often in the community, after years of C it's just very refreshing.

Some things zig is missing:

* Package manager, this is coming soon. [1]

* Missing documentation for inline assembly (I think this part is going to get overhauled, as Andrew Kelley is writing an assembler in zig atm [2]).

I don't know Nim, but I believe it has a garbage collector so it could be tricky to use for kernel programming.

[0] https://github.com/jzck/kernel-rs

[1] https://github.com/ziglang/zig/issues/943

[2] https://www.youtube.com/watch?v=iWRrkuFCYXQ

Re:Hobby x86 kernel written with Zig by simias on Monday January 06, 2020 @10:25AM (#21969767)
It's true that for bare metal programming Rust is quite far from 1.0. You can't even do inline assembly in stable rust...

Arbitrary sized integers do sound very convenient, although I guess the counterargument would be that they simply can't map to machine types so you'll have to have magic behind the scenes which may not make complete sense in low level languages. Still, C has bitfields in structs which are sort of the same thing so why not.

Pointer handling however I'm not sold on. I'd argue that the very cumbersome pointer handling in Rust is a feature, not a bug. It's explicitly discouraged, and for good reasons. Map your pointer into a clean and safe reference, slice or wrapper object ASAP and leave raw pointers to the messy details of C FFIs and ultra low-level code. If you end up having to mess with raw pointers everywhere in your codebase you're doing it wrong, as far as Rust is concerned.

I think this is reasonable even for kernel code. After all typically you wouldn't dereference register pointers directly in C code because of volatility issues and making it obvious that you're accessing hardware and not RAM (readl/writel or similar), so having a safe wrapper adds no overhead in my experience.

Re:Hobby x86 kernel written with Zig by jackhalford on Monday January 06, 2020 @10:57AM (#21970079)
Ok maybe I was user pointers wrong in Rust. And I must confess that I never really understood lifetimes either, however

* I don't think that low level code needs to be "messy", and

* I don't think that adding an abstraction on top solves anything complexity wise.

For memory paging I used to have a whole library in Rust that handled hierarchy of page tables with very abstract pagetable classes and interfaces [0]. Now I just have 80 lines of zig that handles everything [1]. I much prefer the latter.

> I think this is reasonable even for kernel code.

It's not, for some of the reasons that Linus doesn't want C++ code in the kernel [2]

[0] https://github.com/gz/rust-x86/blob/28d5839933973e0b639ef354...

[1] https://git.sr.ht/~jzck/kernel/tree/master/src/arch/x86/pagi...

[2] https://yarchive.net/comp/linux/c++.html

Re:Hobby x86 kernel written with Zig by CDSlice on Monday January 06, 2020 @12:10PM (#21970829)
The Rust code you wrote seems to be extremely heavy on boilerplate. A couple of macros could have drastically reduced the line count.

I don't think what Linus wrote in 2004 on C++ has much relevance for 2020 Rust, especially for a new kernel that doesn't have a ton of contributors. Most of his complaints seem to be on how C++ abstractions can make it hard to review unknown code and how 2004 C++ code bases often had extremely messy code. Rust IMO doesn't suffer from those problems near as much as 2004 C++ did.

Also, Google is using Rust for part of their new Fuchsia kernel so at least they think Rust has something good to offer kernel dev.

Re:Hobby x86 kernel written with Zig by roblabla on Monday January 06, 2020 @12:29PM (#21971007)
Small nitpick: Google isn't using Rust for the Fuchsia kernel. The kernel, Zircon, is written entirely in C and is based on Little Kernel. Google is using Rust for some userspace drivers (Fuchsia being a micro-kernel).
Re:Hobby x86 kernel written with Zig by loeg on Monday January 06, 2020 @05:57PM (#21974442)
> The kernel, Zircon, is written entirely in C

No, it's written in C++[1]. They're very different languages.

That said, Google seems to think C++ has something to offer to all kinds of development. It seems to work for them, but they are a heavily C++ shop. I wouldn't read too much out of their use of C++ anywhere; it'd be more surprising and interesting to see them use anything else.

[1]: https://fuchsia.googlesource.com/fuchsia/+/master/zircon/ker...

Re:Hobby x86 kernel written with Zig by roblabla on Monday January 06, 2020 @06:31PM (#21974766)
Oh that's interesting. Zircon is based on LK[0] which is in C, which is why I thought it was still written in C. I wonder how much is left of the original LK code then.

[0]: https://fuchsia.dev/fuchsia-src/concepts/kernel/zx_and_lk

Re:Hobby x86 kernel written with Zig by loeg on Tuesday January 07, 2020 @01:22AM (#21977458)
Interesting, I didn't know about LK. Thanks for sharing :-).
Re:Hobby x86 kernel written with Zig by nickpsecurity on Monday January 06, 2020 @02:07PM (#21972046)
You might find it helpful to look at Redox OS and Tock to see how they're handling kernel development in Rust. May or may not be easier than what you described.

https://www.redox-os.org/

https://www.tockos.org/

Re:Hobby x86 kernel written with Zig by pcwalton on Monday January 06, 2020 @11:26AM (#21970367)
The problem with C-style bitfield values/arbitrary-sized integers is that they don't have addresses, so you can't take references to them. This means that you need special cases everywhere, and it increases the complexity of the language a lot. In Rust, integers often have methods that take self by reference, which demands an actual address. For these reasons, in Rust I think it's better that arbitrarily-sized integers aren't first class. They're never really first class anyway.
Re:Hobby x86 kernel written with Zig by AndyKelley on Monday January 06, 2020 @12:58PM (#21971288)
In Zig they do; here is an example: https://clbin.com/HlJ0X

Output:

    Test [1/1] test "bit field access"...
    *align(:3:1) u3
    All 1 tests passed.
Zig's pointer type has optional metadata to describe alignment, sub-byte offset, and SIMD vector element index. This generally makes things just work, and you'll get a compile error if you expected an unadorned aligned pointer. If I were to swap:

    - fn deref(ptr: var) u3 {
    + fn deref(ptr: *u3) u3 {
Then I'd get:

    test.zig:13:23: error: expected type '*u3', found '*align(:3:1) u3'
        assert(deref(&data.b) == 2);
                          ^
Re:Hobby x86 kernel written with Zig by simias on Monday January 06, 2020 @03:34PM (#21972986)
How do you deal with atomicity though? A big deal in Rust is that you can only have one mutable reference to an object at any given time which prevents all sorts of aliasing issues, but here if you have two mutable fat pointers, one to and and one to b, and you attempt to write them without synchronization you're going to have a big problem, won't you?

This is the main risk with bitfields in C, it makes reads and writes that appear to be independent actually access the same memory cell behind the scenes. I'm not convinced by the idea of adding even more magic on top of it to be honest, it is true that packing and unpacking bitfields is annoying and requires some boilerplate but in the end that is how it the hardware does it anyway.

Re:Hobby x86 kernel written with Zig by AndyKelley on Monday January 06, 2020 @04:40PM (#21973670)
The builtin functions for atomic operations provided by the language do not allow unaligned pointers, so it would be a compile error.
Re:Hobby x86 kernel written with Zig by zozbot234 on Monday January 06, 2020 @10:59AM (#21970100)
I don't think bitfields are even that portable in C? You end up with a weird mix of bit-endianness and byte-endianness issues. The best approach AFAICT, if you need to model a binary format with arbitrary-sized integers, is to keep the binary structure opaque and have a function to "unpack" it to a record of machine-native types. Modern compilers can easily optimize the resulting code into shifts+bit operations, and it is a reasonably foolproof approach.
Re:Hobby x86 kernel written with Zig by dom96 on Monday January 06, 2020 @10:20AM (#21969742)
You can certainly use Nim for kernels. I've created a proof of concept long ago, and things are only improving with ARC fast becoming a great alternative to Nim's GC.

https://github.com/dom96/nimkernel

Re:Hobby x86 kernel written with Zig by pjmlp on Monday January 06, 2020 @09:47AM (#21969468)
> I don't know Nim, but I believe it has a garbage collector so it could be tricky to use for kernel programming.

Have a look at Project Oberon, Singularity, Interlisp-D, Smalltalk, Modula-3 Topaz, Blue Bottle (AOS), CosmosOS, lilith (Crystal variant) given that their source is available.

Re:Hobby x86 kernel written with Zig by guggle on Monday January 06, 2020 @07:44AM (#21968686)
Thank you.

> I don't know Nim, but I believe it has a garbage collector so it could be tricky to use for kernel programming.

You're right. Still good for libraries though (or apps, but that may be outside of "system").

Re:Hobby x86 kernel written with Zig by alehander42 on Monday January 06, 2020 @08:28AM (#21968936)
you're right, it is confusing, but it is optional: some toy kernels already work in nim , and with latest work on memory, you should be able to use most of the language for kernel development ! not the perfect language for that yet though, but i hope we should see more nim os examples
Re:Hobby x86 kernel written with Zig by Touche on Monday January 06, 2020 @09:09AM (#21969192)
Are you saying that the GC is optional? If you don't use it how do you allocate/free memory?
Re:Hobby x86 kernel written with Zig by mratsim on Monday January 06, 2020 @10:31AM (#21969815)
You call malloc/free, or if working with GPU cudaMalloc/free. You can write your own memory pool or object pools, you can use destructors, and even implement your own reference counting scheme.

This is what I use for my own multithreading runtime in Nim and the memory subsystem makes it faster and more robust than any runtime (including OpenMP and Intel TBB) that I've been benchmarking against, see memory subsystem details here: https://github.com/mratsim/weave/tree/master/weave/memory

Example of atomic refcounting in this PR here: https://github.com/mratsim/weave/blob/025387510/weave/dataty...

Also one important thing, Nim current GC is based on TLSF (http://www.gii.upv.es/tlsf/) which is a memory reclamation scheme for real-time system, it provides provably bounded O(1) allocations. You can tune Nim GC with max-pauses for latency critical applications.

Re:Hobby x86 kernel written with Zig by Touche on Monday January 06, 2020 @11:49AM (#21970609)
Does the standard library use malloc/free or does it depend on the GC? This is the part that's puzzling to me, if the stdlib depends on GC then it's harder to say that GC is optional. Technically optional but not super practical.
Re:Hobby x86 kernel written with Zig by nimmer on Monday January 06, 2020 @02:07PM (#21972040)
The majority of stdlib modules does not depend on GC.

Also, the new ARC memory manager replaces GC and can run in a kernel.

Re:Hobby x86 kernel written with Zig by mratsim on Monday January 06, 2020 @03:48PM (#21973129)
No that's not true.

As soon as you use sequences or strings or async you depend on the GC.

You can however compile with gc:destructors or gc:arc so that those are managed by RAII.

Re:Hobby x86 kernel written with Zig by nimmer on Monday January 06, 2020 @06:28PM (#21974729)
I used various modules with gc:none

I meant that new ARC GC, that will replace the current one, can be used for the kernel.

It's still a GC, technically, but, quoting Araq on ARC:

Nim is getting the "one GC to rule them all". However calling it a GC doesn't do it justice, it's plain old reference counting with optimizations thanks to move semantics.

Re:Hobby x86 kernel written with Zig by alehander42 on Monday January 06, 2020 @10:07AM (#21969639)
overally i think for small os-es you can write easily a micro libc core in C or even in Nim where you define malloc/free etc and just use them directly as in C and zig.

otherwise you should be able to use something like destructors eventually

Re:Hobby x86 kernel written with Zig by guggle on Monday January 06, 2020 @09:53AM (#21969524)
Didn't test this but some clues: https://nim-lang.org/docs/gc.html

Then I guess you would use new/dealloc

Re:Hobby x86 kernel written with Zig by guggle on Monday January 06, 2020 @10:04AM (#21969617)
Re:Hobby x86 kernel written with Zig by joshbaptiste on Monday January 06, 2020 @08:27AM (#21968932)
Nim's GC is optional
Re:Hobby x86 kernel written with Zig by Touche on Monday January 06, 2020 @09:11AM (#21969204)
How can it be optional if there is lots of code that assumes you are using GC? For example, as far as I can tell the stdlib doesn't do its own allocations. Does this mean you can't use the stdlib with GC disabled? Or am I missing something here?
Re:Hobby x86 kernel written with Zig by qmmmur on Monday January 06, 2020 @09:55AM (#21969540)
A friend of mine wrote a DSL for audio using Nim with GC off.

https://github.com/vitreo12/omni

I don't know enough to comment but it may be useful to look at things in the wild. This project also heavily relies on calling in C to interface with environments and the SuperCollider scsynth.

Re:Hobby x86 kernel written with Zig by alehander42 on Monday January 06, 2020 @10:12AM (#21969679)
overally if you write an os you need to write your malloc/free .. and then you should be able (i think..) to use many of the gc-s anyway and more of stdlib.

But you can also think of Nim as macro-able higher level C and write it like that (but indeed you probably still need this minimal allocation support)

Re:Hobby x86 kernel written with Zig by sov on Monday January 06, 2020 @04:26PM (#21973515)
Your point about unsafe pointer handling in Rust is specifically what dissuaded us from using it in an upcoming project. It really feels bad prepending all of the code that you actually care about being safe with `unsafe`.
Re:Hobby x86 kernel written with Zig by senderista on Monday January 06, 2020 @06:20PM (#21974657)
`unsafe` before a block simply means "the following code has been manually checked for memory safety, because the compiler is unable to automatically do so". Before a function it means "this can only be called from an unsafe block, because the compiler cannot enforce the preconditions it requires to ensure memory safety". What alternative term would make you feel less bad?
Re:Hobby x86 kernel written with Zig by UncleOxidant on Monday January 06, 2020 @01:02PM (#21971339)
> * zig has native support for arbitrary sized integers

That's really cool.

Re:Hobby x86 kernel written with Zig by bogomipz on Monday January 06, 2020 @03:55PM (#21973204)
>"zig has native support for arbitrary sized integers."

I apologize if this is a naive question. Might you or someone else elaborate on where arbitrary sized integers are used or necessary in kernel programming? Don't they all ultimately need to get padded out for the CPU registers to work with them anyway?

Re:Hobby x86 kernel written with Zig by loeg on Monday January 06, 2020 @06:01PM (#21974488)
Packed binary structures. Common in device register space (the author alludes to PCI driver(s)).
Re:Hobby x86 kernel written with Zig by eli_gottlieb on Monday January 06, 2020 @01:58PM (#21971937)
Can Zig's enums and tagged unions be used to write algebraic data types?
Re:Hobby x86 kernel written with Zig by sanxiyn on Tuesday January 07, 2020 @02:07AM (#21977705)
Yes it can.
Re:Hobby x86 kernel written with Zig by eli_gottlieb on Thursday January 09, 2020 @01:42PM (#22004088)
Very nice!
Re:Hobby x86 kernel written with Zig by rapsey on Monday January 06, 2020 @08:40AM (#21969010)
On the other hand safe Rust code will be memory safe..
Re:Hobby x86 kernel written with Zig by pron on Monday January 06, 2020 @08:52AM (#21969093)
It's Zig's goal to make it easy to write safe code, it's just that its approach to safety is very different from Rust's.
Re:Hobby x86 kernel written with Zig by woodrowbarlow on Monday January 06, 2020 @09:12AM (#21969214)
not if you have to keep using unsafe blocks.
Re:Hobby x86 kernel written with Zig by cies on Monday January 06, 2020 @09:25AM (#21969299)
As an ancestor in this thread pointed out: it's hard/impossible to go without unsafe in a kernel.

Proof (and they have a policy to only use unsafe when not otherwise possible):

https://github.com/redox-os/kernel/search?q=unsafe&unscoped_...

Re:Hobby x86 kernel written with Zig by woodrowbarlow on Monday January 06, 2020 @09:37AM (#21969387)
yes, that's the point i'm making.

saying "if you write your kernel in rust, it will be memory safe" is a little goofy, because writing kernels in rust requires you to drop into unsafe blocks way too often.

Re:Hobby x86 kernel written with Zig by bluejekyll on Monday January 06, 2020 @09:44AM (#21969439)
Not as often as you would think, Phill Oppermann has shown this: https://os.phil-opp.com/

Unsafe is definitely used, but it does not mean what I think is being implied, which is that it makes the software unsafe.

Re:Hobby x86 kernel written with Zig by pjmlp on Monday January 06, 2020 @09:49AM (#21969487)
As proven by the Oberon and Xerox PARC workstations, the amount of unsafe code is quite minimal.

Source code is available.

Re:Hobby x86 kernel written with Zig by rapsey on Monday January 06, 2020 @09:47AM (#21969467)
The ammount of unsafe blocks is pretty small compared to the rest of the code. Which means those parts can be reviewed more extensively. If anything your argument is pretty goofy.
Re:Hobby x86 kernel written with Zig by phendrenad2 on Monday January 06, 2020 @02:22PM (#21972194)
What I find is that, while Rust unquestionably improves the security of software, the argument that you can minimize unsafe code and thoroughly inspect it doesn't tell the full story, because in my experience people end up writing general-purpose unsafe blocks, and while they are controlled by the "safe" outside, the sequence of actions the "safe" code tells them "unsafe" code to execute can lead to subtle bugs.
Re:Hobby x86 kernel written with Zig by celticmusic on Monday January 06, 2020 @03:25PM (#21972901)
yep, 100%

I personally think the Rust community has missed the biggest advantage of unsafe, and that's tooling. Imagine an IDE that could highlight and warn when touching state that is accessed in unsafe blocks, or when state being accessed in unsafe blocks isn't being properly tested.

Or tools that kick off notification for a required code review because code that touches state used by an unsafe block got changed.

To me, these sorts of things will have a far greater effect on security and stability than just the existence of the unsafe block in code.

Re:Hobby x86 kernel written with Zig by Cyph0n on Monday January 06, 2020 @04:20PM (#21973450)
But isn’t the most important step having a compiler-enforced keyword for code that seems to be doing unsafe stuff?

The rest are just plugins and tools that look for said blocks and “do stuff”. I don’t see why these tools should be part of the language.

Re:Hobby x86 kernel written with Zig by celticmusic on Monday January 06, 2020 @04:56PM (#21973832)
I never said they should be part of the language, I said the rust community over-emphasizes the "safe" part of having the unsafe keyword and neglected the part that's actually useful in keeping things safe.

Another poster responded to me stating they are starting to work on those tools, so it looks like the rust community is starting to come around.

It's just that I've seen a lot of people tout the safety of rust as if the unsafe keyword was a panacea that automatically prevented problems. That attitude is really what I was responding to.

Re:Hobby x86 kernel written with Zig by Cyph0n on Monday January 06, 2020 @05:07PM (#21973995)
Ah, gotcha. Yes, improved tooling in this area would add a lot of value to the feature.
Re:Hobby x86 kernel written with Zig by steveklabnik on Monday January 06, 2020 @04:51PM (#21973786)
Those tools exist or are being worked on! Servo (last I checked) uses a “unsafe code was added or modified in this PR, please review extra carefully” bot, and Miri is on its way to becoming a fantastic tool.
Re:Hobby x86 kernel written with Zig by celticmusic on Monday January 06, 2020 @05:45PM (#21974334)
That's good to know.

The Rust community can be a bit... fanatical, lets say. I've seen so many people argue that the unsafe keyword by itself makes rust so much safer than alternatives, when really safe code can cause unsafe code to explode. So you end up writing modules to protect the unsafe code, which is what you do in any other language as well.

Which means the unsafe keyword is valuable, just not nearly as valuable as I've seen a lot of people claim.

Now the unsafe keyword combined with the sort of tooling I've mentioned? That to me is a killer combination. Unsafe isn't a silver bullet, it still requires work, but it enables tools to make that work immensely easier to deal with.

Re:Hobby x86 kernel written with Zig by DSMan195276 on Monday January 06, 2020 @01:23PM (#21971543)
> The ammount of unsafe blocks is pretty small compared to the rest of the code. Which means those parts can be reviewed more extensively. If anything your argument is pretty goofy.

I've talked about this at length before, but just because you have 'less' `unsafe` code does not mean you code is any better off. Very few operations are actually `unsafe`, and you're still not allowed to break any of the (poorly or not documented) constraints that Rust requires even in `unsafe` code.

Point being, unless you can make "safe" abstractions around your `unsafe` code that cannot be broken (Which is largely what you do in any language anyway, when you can...), the distinction between "safe" and "unsafe" is thin, because the parts of the code you declare `unsafe` are not the parts that are actually going to have the bugs. And because of the fact that the constraints you are required to fulfill in `unsafe` code is unclear, there's little way to guarantee your code is broken in some subtle way the optimizer might jump on (either now, or in a later release).

And to be clear, I like Rust, but `unsafe` is poorly thought-out IMO.

Re:Hobby x86 kernel written with Zig by zozbot234 on Monday January 06, 2020 @02:02PM (#21971982)
If you aren't making a safe abstraction around your unsafe code, you're supposed to mark the surrounding function "unsafe" as well, and document its expected constraints in a 'Safety' comment block. In fact, you're supposed to do the same for any piece of code that cannot provide safety guarantees about its use of 'unsafe' features. 'Plain' unsafe{ } blocks should only be used as part of building a safe abstraction.
Re:Hobby x86 kernel written with Zig by DSMan195276 on Monday January 06, 2020 @05:37PM (#21974262)
> If you aren't making a safe abstraction around your unsafe code, you're supposed to mark the surrounding function "unsafe" as well, and document its expected constraints in a 'Safety' comment block.

Sure, but now we're back to the issue that it's unclear what constraints `unsafe` code actually has to hold, meaning ensuring your abstraction is safe is just about impossible to do with certainty (And OS kernel code is definitely going to hit on a lot of those corner cases). You may think you have a safe interface, only to find out later you're relying on internal details that aren't guaranteed to stay the same between compiler versions or during optimizations.

With that said, while I agree with what you're proposing about marking surrounding code "unsafe", it leads to lots of strange cases and most people get it wrong or will even disagree with this proposal completely. For example, it can lead to cases where you mark a function `unsafe` even though it contains nothing but "safe" code. And at that point, it's up to you to determine if something is actually "safe" or "unsafe", meaning the markings of "safe" and "unsafe" just become arbitrary choices based on what you think "unsafe" means, rather than marking something that actually does one of the "unsafe" operations.

One of the best examples is pointer arithmetic. It's explicitly a safe operation, but it's also the first things most people would identify as being "unsafe", even though it is dereferencing that is the "unsafe" operation. Ex. You could easily write slice::from_raw_parts without using any `unsafe` code at all, it just puts a pointer and length together into a structure (it doesn't even need to do arithmetic!). It's only marked `unsafe` because it can break other pieces of "safe" code that it doesn't use, but it itself is 100% safe. You could just as easily argue the "other code" should be the `unsafe` code, since it's what will actually break if you use it incorrectly.

Perhaps the biggest annoyance I have is that the official Rust documentation pretty much goes against the idea you presented, saying

> People are fallible, and mistakes will happen, but by requiring these four unsafe operations to be inside blocks annotated with unsafe you’ll know that any errors related to memory safety must be within an unsafe block. Keep unsafe blocks small; you’ll be thankful later when you investigate memory bugs.

Which is just incorrect - memory safety issues are likely to be due to the "safe" code surrounding your unsafe code - when you're writing C code, the bug isn't that you dereferenced NULL or an OOB pointer, the bug is the code that gave you that pointer in the first place, and in Rust that code is likely to all be "safe". Point being, most of the advice on keeping your unsafe blocks small just leads to people making silly APIs that can be broken via the safe API they wrap (Even in weird ways, like calling a method containing only safe code), and unfortunately there are lots of subtle ways you can unintentionally break you Rust code that most people aren't going to have any idea about.

Re:Hobby x86 kernel written with Zig by zozbot234 on Monday January 06, 2020 @06:05PM (#21974529)
> Sure, but now we're back to the issue that it's unclear what constraints `unsafe` code actually has to hold

The Rust Nomicon actually documents these constraints for each 'unsafe' operation. Code that can ensure that these constraints hold can be regarded as 'safe' and rely on an unsafe{ } block. Code that can't, should be marked unsafe and document its own constraints in turn.

> You may think you have a safe interface, only to find out later you're relying on internal details

If you're relying on internal details, you're most likely doing it wrong, even by the standards of 'unsafe'. There are ways to nail down these details where required, at which point they're not even "internal" details anymore, but this has to be opted-into explicitly, for sensible reasons.

> It's only marked `unsafe` because it can break other pieces of "safe" code that it doesn't use, but it itself is 100% safe. You could just as easily argue the "other code" should be the `unsafe` code, since it's what will actually break if you use it incorrectly.

No, "other code" should not be marked unsafe because this would mean that relying on slices is inherently unsafe. Which is silly; the whole point of a "slice" type, contrasted with its "raw parts", is in the guarantees it provides. This is why slice::from_raw_parts is unsafe: not because if what it does, but what it states about the result.

> Which is just incorrect - memory safety issues are likely to be due to the "safe" code surrounding your unsafe code

This is a matter of how you assign "blame" for a memory safety error. It's definitely true however, that the memory unsafe operations play a key role, and I assume that's the point that these docs are making. I agree that people shouldn't be creating faulty abstractions around "unsafe" blocks, but the reason we can even identify this as an issue is because we have "unsafe" blocks in the first place!

Re:Hobby x86 kernel written with Zig by DSMan195276 on Monday January 06, 2020 @09:17PM (#21976106)
> The Rust Nomicon actually documents these constraints for each 'unsafe' operation. Code that can ensure that these constraints hold can be regarded as 'safe' and rely on an unsafe{ } block. Code that can't, should be marked unsafe and document its own constraints in turn. > > If you're relying on internal details, you're most likely doing it wrong, even by the standards of 'unsafe'. There are ways to nail down these details where required, at which point they're not even "internal" details anymore, but this has to be opted-into explicitly, for sensible reasons.

It's not that simple, what I'm getting at is that 'unsafe' code is still required to meet all the constraints that 'safe' code has to meet, even though those constraints are not fully documented (Because in general, in 'safe' code there are lots of things that are impossible to do but not explicitly UB), and they have changed them in significant ways in the past. You can see an incomplete list of them here[0]. There has been work to nail these details down for years, with the most recent being this one[1], but in general I would argue there hasn't been much progress since I first looked into it years ago now. Fun fact, the first Rust issue I remember reading that touched on this (and evolved into basically everything going on today) is this[4] one, which was made exactly 4 years ago today (And I mean exact!).

The 'Safety' sections for 'unsafe' operations are actually just extra requirements for those particular API on top that you also have to keep in addition to whatever safe Rust constraints apply. And I would argue in general they're not actually exhaustive, and they're mostly just a "best guess" - it's not a breaking change to introduce or realize there are more constraints. Ex. A little more then a year ago, they added the info that a slice can't be larger than `isize::MAX`[2], and a few months ago was this[3] one, where they added that the pointer length must be from a single allocation.

My point is that when you're doing something like writing an OS kernel, you end up running into these types of issues and corner-cases because Ex. They determine what your allocator is allowed to produce, or if some particular pointer can be validly put into a slice.

> No, "other code" should not be marked unsafe because this would mean that relying on slices is inherently unsafe. Which is silly; the whole point of a "slice" type, contrasted with its "raw parts", is in the guarantees it provides. This is why slice::from_raw_parts is unsafe: not because if what it does, but what it states about the result.

Sure, my point is that this is at best unclear, and I would argue wildly misunderstood. We've basically made two definitions of 'unsafe' - one for code that has to perform one of the five 'unsafe' operations (Which is a clearly defined 'yes' or 'no', and also the one described by the Rust docs), and one for code that can potentially break some type of invariant the will potentially cause UB somewhere else in the program (even if the code in question is completely safe and can't on its own cause any problems).

The problem is that Rust only guarantees the type of safety that it does if you do the second, but yet lots of people (I would almost argue most...) assume the first is sufficient and 'unsafe' by itself ensures all your memory bugs are in `unsafe` code. The Rust docs even describes safe vs. unsafe as two completely different languages (which they are) which brings into question why you would write `unsafe` code when you don't even need any of the `unsafe` operations. You arguably want a separate keyword for this - one that marks a function `unsafe`, but doesn't actually allow you to perform any of the `unsafe` operations in it.

[0] https://doc.rust-lang.org/nomicon/what-unsafe-does.html [1] https://github.com/rust-lang/unsafe-code-guidelines [2] https://github.com/rust-lang/rust/commit/1975b8d21b21d5ede54... [3] https://github.com/rust-lang/rust/commit/1a254e4f434e0cbb9ff... [4] https://github.com/rust-lang/rfcs/issues/1447

Re:Hobby x86 kernel written with Zig by zozbot234 on Monday January 06, 2020 @09:45PM (#21976292)
> - it's not a breaking change to introduce or realize there are more constraints.

These breaking changes are allowed precisely because they're meant to address soundness concerns. Even if at any given time they're just a "best guess" of what's actually needed, that's still wildly better than what e.g. C/C++ do, which is just to not guess at all. Even wrt. formal memory models, perhaps the most complex issue among the ones you mention here, C/C++ only got an attempt at a workable memory model with the 201x standards (C11, etc.). It's not unreasonable to expect that Rust may ultimately do better than that.

Re:Hobby x86 kernel written with Zig by DSMan195276 on Monday January 06, 2020 @10:57PM (#21976689)
I highly disagree with that assessment, in regards to C you're comparing apples to oranges IMO, because C is very lax compared to what Rust enforces now and could enforce in the future, just by virtue of having so many less actual features. With that, if you commit to being `gcc` (and `clang`) specific, you have a very high number of guarantees and flexibility, even more depending on what extra feature flags you pass.

> It's not unreasonable to expect that Rust may ultimately do better than that.

But, when? Rust is almost 10 years old, and questions about this stuff has been posed for years now - I was having these same conversations over two years ago. Last year's roadmap included working on the 'unsafe guidelines' I posted, and as an outsider looking in it's unclear to me how much progress has actually being made. Don't get me wrong - they've made a fair amount of progress, but there's still a lot to be done.

My big concern (which I don't consider to be unfounded) is that because there aren't that many big users of Rust actually developing really low-level stuff, the work on defining it isn't getting done. But to me it feels like a self fulfilling prophecy - a project like the Linux Kernel isn't going to end-up using Rust if there's big issues with connecting what they're doing now to Rust idioms (Ignoring the LLVM thing...), even though when Rust was first maturing it was being billed as the replacement for C (and still is).

Re:Hobby x86 kernel written with Zig by pjmlp on Tuesday January 07, 2020 @04:00AM (#21978239)
Well, it took 40 years for C to have any kind of memory model, so Rust has still some time available.
Re:Hobby x86 kernel written with Zig by DSMan195276 on Tuesday January 07, 2020 @09:09AM (#21980003)
That's a bit hand wavy - The Linux Kernel has been doing atomics in C for I believe over 20 years now, well before C11 came out. And even after it came out they don't use C11 atomics. `gcc` gives them enough guarantees on its own to implement correct atomics and define their own memory model, in some ways better then the C11 one (and in some ways worse, but mostly just from an API standpoint).

When that said, my concerns are more with the state of `unsafe` overall, the memory model is only one part of that (though it somewhat spurred conversions about the other issues).

Re:Hobby x86 kernel written with Zig by pjmlp on Tuesday January 07, 2020 @10:47AM (#21981028)
Linux kernel is just one kernel among many.

I bet that the pre-C11 memory semantics on Linux kernel aren't the same as e.g. on HP-UX kernel, across all supported hardware platforms.

It is also ironic that Java and .NET did it before C and C++, with their models being adopted as starting point.

Re:Hobby x86 kernel written with Zig by DSMan195276 on Tuesday January 07, 2020 @05:39PM (#21985920)
> Linux kernel is just one kernel among many.

> I bet that the pre-C11 memory semantics on Linux kernel aren't the same as e.g. on HP-UX kernel, across all supported hardware platforms.

Yeah, but if they both work, why does it matter? There exists more than one valid possible memory model. The point I was making is that with C, it has been possible to define valid semantics well before C11 because compilers and the language give enough guarantees already.

To that point, while I focused on atomics (Since atomics and threading are largely what was added in C11), the bulk of the actual memory model that made that possible was defined well before then. Strict-aliasing was a thing in C89 (Though I'm not sure compilers enforced it at that point) and is probably the only real notable "gotcha", and `gcc` and `clang` let you turn it off outright (And if you're not doing weird kernel stuff, it generally is easy to obey).

`gcc` and `clang` also have lots of documentation on the extra guarantees they give, such as very well documented inline assembly, type-punning via `union`, lots of attributes for giving the compiler extra information, etc.

Compared to what Rust offers right now, there is no comparison. With Rust, the aliasing model (Which `unsafe` code is not allowed to break) is still unknown, and a lot of the nitty/gritty details like the stuff I listed for `from_raw_parts` are simply leaky implementation details they're inheriting unintentionally from LLVM (And effectively from C - C is where things like "not allowed to go past one past the end of an allocated block" restriction comes from, along with a host of other things).

Re:Hobby x86 kernel written with Zig by pjmlp on Wednesday January 08, 2020 @04:42AM (#21989902)
While I do agree that Rust still needs to improve in this area, what happens when you port pre-C11 from gcc on Linux (x86) to aC on HP-UX (Itanium) and xlc on Aix (PowerPC), to keep up with my example?
Re:Hobby x86 kernel written with Zig by DSMan195276 on Wednesday January 08, 2020 @01:02PM (#21993980)
Well, I would clarify - In reference to the memory models I was talking about, it only applies to code written as part of those kernels, userspace code does not "inherit" that memory model. And I don't think portability of kernel code is much of a concern, the memory model is only one of a large variety of problems.

That said, for userspace, pthreads already gives enough guarantees on ordering via mutex's that there aren't really any problems unless you're trying to do atomics in userspace, which before C11 would have been hard to do portability. And the rest of the things I was talking about like the aliasing model are defined as part of the C standard (C89 and C99), so it's always the same regardless of the compiler (ignoring possible bugs).

Re:Hobby x86 kernel written with Zig by swsieber on Monday January 06, 2020 @02:09PM (#21972058)
It doesn't mean your code is better off, but it makes auditing your code a lot easier. Rust gives you lots of good tools so that you can fence in your unsafe and restrict it to a module (source code file), such that if you audit the module containing the unsafe, you should be good to go.
Re:Hobby x86 kernel written with Zig by celticmusic on Monday January 06, 2020 @03:22PM (#21972860)
The value is in knowing where the unsafe operations are.

It could be argued that's not a huge value gain since you're so far down the stack, but that's the value of the unsafe keyword.

Re:Hobby x86 kernel written with Zig by gameswithgo on Monday January 06, 2020 @03:52PM (#21973176)
the million dollar question is what % of your code has to be unsafe in a kernel. At some %, it ends up not being worth the trouble. But if the % can be kept relatively low (say 5% or less) then you get a lot of value out of minimizing the surface area of the dangerous code.
Re:Hobby x86 kernel written with Zig by Cyph0n on Monday January 06, 2020 @04:17PM (#21973418)
I was expecting way more “unsafe” uses than that!

I see two advantages to explicitly marking code as unsafe:

(1) You are very clearly marking the areas of code that are most suspect

(2) You can still have safe code outside of those “unsafe” blocks

Re:Hobby x86 kernel written with Zig by nickpsecurity on Monday January 06, 2020 @02:17PM (#21972147)
Unsafe blocks don't mean it's unsafe: just that the language's safety scheme can't prove anything about that particular block. You prove each safe externally with Rust's safety handling everything else (i.e. majority of the code).

You can also go further like I did here:

https://news.ycombinator.com/item?id=21840431

You're still worrying about a smaller portion of your code.

Re:Hobby x86 kernel written with Zig by nimmer on Monday January 06, 2020 @02:11PM (#21972075)
Same for many other languages... as far as memory safety goes because there are performance tradeoffs in a kernel.
Re:Hobby x86 kernel written with Zig by celticmusic on Monday January 06, 2020 @03:18PM (#21972801)
I looked over zig when it was first announced and really liked the idea, it was just too fresh for me to work with.

But I've always had a mental note to go back once it got a bit more stable because I really do like the ideas behind it.

It sounds to me like it's definitely getting there and that's awesome.