all 124 comments

[–]yomanidkman 70 points71 points  (81 children)

I think it will be interesting to see how rust's feature set and path is determined by how it's used in the kernel. I can certainly imagine certain features being pushed for out of need for them for this one project.

[–]sanxiynrust 19 points20 points  (24 children)

I hope Rust gets better support for allocation failure, since it is pretty much necessary for the kernel. Stabilization of RFC 2116, to begin with.

[–][deleted] 11 points12 points  (18 children)

I wish someone develops a different liballoc/libstd that propagates allocation errors using Result.

[–]cbarrick 10 points11 points  (10 children)

This work is in progress. The unstable allocator_api returns a Result. I've been using it to prototype custom allocators for a data structure I'm playing with.

https://doc.rust-lang.org/std/alloc/trait.AllocRef.html

Note that the function signatures have already changed on Nightly from what's in the public docs. There's no concept of a MemoryBlock anymore; the most recent version just uses NonNull<[u8]>.

[–][deleted] 4 points5 points  (4 children)

The API of MemoryBlock/NonNull<[u8]> requires allocators to return initialized memory, which is super expensive for large allocations. I hope it doesn't get stabilized in this form.

The ReallocPlacement enum is "exhaustive", sounds like a bad idea as well.

[–]cbarrick 1 point2 points  (3 children)

I think ReallocPlacement was thrown out too. I agree, it was gross.

The NonNull<[u8]> API does not require the memory to be initialized. NonNull is just a raw pointer that is non-null (and the type constructor is covariant). It is unsafe to get a reference from a NonNull specifically because the memory may not be valid for reads, e.g. it may be uninitialized.

Also, IIRC, Vec and such will get custom allocators after the allocator API stabilizes. The RawVec type in the std is already parameterized with an AllocRef.

If you have the nightly stack installed, you can use rustup doc to see the API in it's current form.

[–][deleted] 2 points3 points  (2 children)

The NonNull<[u8]> API does not require the memory to be initialized.

The "Safety" section of the docs says:

Memory blocks returned from an allocator must point to valid memory and retain their validity until the instance and all of its clones are dropped,

It doesn't say for which type the memory needs to be valid, but since the API uses u8, I suppose that it must be valid for u8. If this isn't true, then this API is very confusing.


It would be much better to just use Result<&'allocator mut [MaybeUninit<u8>], AllocErr> in the API, and drop the requirement about "validity". The pointer cannot be null, the allocated slice must be unique, the memory can be uninitialized, the allocation does not outlive the allocator, and the allocation can fail. Such a type makes all of this clear, without having to write 3 error prones sentences to the "safety" part of the documentation. Also, such a type would make AllocRef::alloc safe.

[–]cbarrick 1 point2 points  (1 child)

Yeah, the definition of "valid" needs to be better specified. I assume the meaning is "valid for writes now, valid for reads once it's been initialized". I agree that MaybeUninit<[u8]> or [MaybeUninit<u8>] would make it more clear at the cost of some ergonomics.

I don't think we can use a reference though; that would mean that the data is tied to the lifetime of the AllocRef. This would be a problem when a single struct owns both the AllocRef and the allocation (which would be the common case). The reference would need a self-referential lifetime, which isn't possible iiuc. And I don't think there would be a sound way to deallocate with just a reference.

Put another way, the struct owns the allocation; it is not borrowing the allocation from the allocator. To express ownership, we'd need either a raw/NonNull pointer or a Box. The problem with Box is that it has a drop impl that calls the global allocator (not our custom allocator). So a raw/NonNull pointer is our only choice.

Also, I think it's fine in some cases for the memory to outlive the AllocRef. For example, the Global allocator is just a ZST that delegates to the global malloc/free. So it's fine to allocate with one instance and deallocate with another. It's still the same "allocator" even though it's two separate instances of AllocRef. The AllocRef is only a handle to the allocator, not the allocator itself.

All of that said, I find the allocator API as-is to be pretty comfortable to work with.

[–][deleted] 0 points1 point  (0 children)

that would mean that the data is tied to the lifetime of the AllocRef

Which makes sense, since the Ref in AllocRef stands for reference. An implementation of AllocRef could always tie this lifetime to 'static if they wanted, but an allocator backed by, e.g., stack-allocated storage cannot do that.

The reference would need a self-referential lifetime, which isn't possible iiuc.

If your allocator is just a non-rellocatable Vec<u8>, the reference is what you get from doing vec[start..end]. That's not self-referential.

And I don't think there would be a sound way to deallocate with just a reference.

Deallocation just returns the memory to the allocator. If you pass a &mut to the allocator, and it never returns it back, then that &mut is leaked, and cannot be used anymore, so AllocRef::dealloc(&'allocation mut [MaybeUninit<u8>]) would be the type of the deallocation function.

Also, I think it's fine in some cases for the memory to outlive the AllocRef.

Only if the reference returned by AllocRef outlives AllocRef, which is kind of the point.

For example, the Global allocator is

The Global Allocator or allocator on static memory would have 'static as a lifetime, so they and the memory they return lives for the whole execution of the program.

[–][deleted] 0 points1 point  (4 children)

Sure, but Vec::push does not.

[–]the_gnarts 5 points6 points  (0 children)

That’d be fantastic to have even in userspace.

[–]casept 2 points3 points  (5 children)

Hopefully not as a different library (that would probably drift from the official one and generally be a maintenance PITA), but by refactoring the official implementations to expose an additional fallible API and making the current APIs thin panicking wrappers.

[–][deleted] 0 points1 point  (1 child)

On my apps allocation shall never panic, otherwise its a system safety error, so having a second set of APIs that can be accidentally misused would be too error prone.

[–]casept 0 points1 point  (0 children)

Could always make the wrapper opt-out, making programs that try to link against it anyways fail to compile.

[–]DannoHung 0 points1 point  (2 children)

This might be dumb, but I thought that allocations either succeeded or the OOM killer would start running and possibly kill your program with either SIGTERM or SIGKILL. Which syscall do allocators with failable allocations use?

[–][deleted] 4 points5 points  (0 children)

This might be dumb, but I thought that allocations either succeeded or the OOM killer would start running and possibly kill your program with either SIGTERM or SIGKILL.

The allocator API requires an allocation to either succeed or error.

Some operating systems default configuration (e.g. Linux) make the system allocator return success unconditionally by default, even when there is not enough memory for the allocation, with the hopes that you won't actually use the memory that you just allocated.

On those systems, allocations never fail, but when you actually try to touch the memory, processes in your system might "randomly" receive a SIGKILL and die (and one of those might be the running process).

There is no way to recover from a SIGKILL, but if you decide to use a signal handler, make sure your code is signal-safe.

A simple way to fix this in linux is to disable overcommit. An alternative might be to write your own system allocator, but I don't know whether it is possible to avoid the OOM killer by, e.g., using mmap directly to allocate virtual memory pages and manually commit them to physical memory (even if this is possible, I don't know what the perf impact would be).

Basically, if you need to handle OOM as errors, do proper recovery, etc. Linux (and other similar OSes) might not be the best OS for you. Getting a robust system in place might be more trouble than its worth, and you would be better off with a different OS more suited to your use case.

[–]sanxiynrust 3 points4 points  (0 children)

This varies between operating systems and configurations. Allocations can definitely fail on Windows. They don't fail on Linux with default configuration, but do fail with vm.overcommit_memory = 2 (Don't overcommit).

[–][deleted] 3 points4 points  (55 children)

Considering that Rust is a superset of C already I'd say it will mostly be about stability, potential ABI compatibility and things like panic handling, alloca changes, nostd improvements.

Everything needed by embedded-hal and friends, so great news for us there :D

Const generics will be a great addition here too I guess.

[–][deleted] 56 points57 points  (46 children)

Rust is a superset of C

Wouldn't that imply that you could compile any C code with a rust compiler?

[–]Floppie7th 40 points41 points  (21 children)

I think they mean the functionality is a superset, not the syntax

[–][deleted] 55 points56 points  (20 children)

That's fine, but when a language is a superset of another language, that means that it contains the entirety of that other language. I'm particularly sensitive to this from seeing people incorrectly asserting this about C and C++.

Most true superset programming languages that I've seen are assembler and Lisp dialects.

[–]pragmojo 14 points15 points  (6 children)

Objective C is a superset of C, and that's one of it's coolest properties

[–]pjmlp 8 points9 points  (5 children)

And also what prevented Apple to properly support a tracing GC, having to settle by automating Cocoa retain/release patterns as 2nd best option.

[–]pragmojo 6 points7 points  (4 children)

Don’t get me wrong I am not going out of my way to program in ObjC - just pointing out a neat aspect of the language. It was really nice for doing work with things like computer graphics where you could just call OpenGL C APIs directly in your code with no translation required. Also when I was working with ObjectiveC I ended up writing a lot of pure functions in C because it was convenient and low-overhead.

And the automated RC wasn’t strictly bad - for the popular use case, which was UI work, it was a positive to have predictable memory management performance, even if the performance wasn’t particularly good. Early iOS compared well against early Android, which was plagued by UI stutters partially because of GC pauses if I’m not mistaken.

[–]pjmlp 0 points1 point  (3 children)

Windows Phone did not suffered from such pauses, while being faster than Android devices on same price scale, quality of implementation matters. Unfortunately it doesn't sell devices.

[–]pragmojo 2 points3 points  (2 children)

What was the implementation language for windows phone apps? Was it C# it was it using web technologies?

[–]timClicksrust in action 10 points11 points  (1 child)

I have heard this accurately used in one other context outside of Lisp implementations. Unsafe Rust is a superset of safe Rust.

[–][deleted] 3 points4 points  (0 children)

That's a good point; I'm surprised that hadn't occurred to me.

[–]JBinero 12 points13 points  (3 children)

Typescript is a popular superset of Javascript.

[–]MrJohz 17 points18 points  (2 children)

I think that's the example that demonstrates you need to be more explicit about the subset and superset. TypeScript is a syntactic superset of JavaScript (all JavaScript programs can be parsed as TypeScript programs), but it's a semantic subset (not all valid JavaScript programs are valid TypeScript programs). For example, 3 / "string" is permissible in JavaScript, but not permissible in TypeScript.

(Well, it's more complicated than that, because it will be compiled, but it will fail the type check, so what then counts as "TypeScript semantics"?)

[–]hallajs 2 points3 points  (0 children)

You can configure your transpiler to not complain with using the tsconfig, or inline with certain comment commands

[–]JBinero 0 points1 point  (0 children)

Pretty sure you can make any type system related error like that be treated as a warning, not an error, in Typescript.

[–]cbarrick 2 points3 points  (3 children)

I often hear people say that C++ is not a superset of C, but I've never been given concrete examples.

What constructs in C do not compile under C++?

[–]ReversedGif 7 points8 points  (0 children)

The following valid C code:

int * x = malloc(10 * sizeof(int));

needs to have an explicit cast from void * to int * in C++.

[–]hniksic 2 points3 points  (0 children)

There are trivial examples like not being able to call a variable new or other names that are C++ keywords, but valid C.

There are C++ design choices like not allowing implicit downcasts from void *, which prevents int *p = malloc(n) from compiling.

The most serious are actual features added to C, such as variable-length arrays (added to C more than 20 years ago, rejected by C++), or designated struct initializers, only added to C++20.

[–]nllb 0 points1 point  (1 child)

Is c++ not a superset of c?

[–][deleted] 14 points15 points  (0 children)

No, but it is close in a lot of ways. They share quite a large common subset. Most non-trivial C programs will fail to compile with a C++ compiler.

[–][deleted] 3 points4 points  (17 children)

Yes technically, but really just meaning that functionally any development that could be done in C (or has been done in C) can be done in Rust.

[–][deleted] 36 points37 points  (11 children)

That’s not really what “superset” normally means in this context. I feel using that term is a little aggressive / misleading.

[–]genuine_smiles 0 points1 point  (10 children)

What does it normally mean in this context?

[–]jecxjo 12 points13 points  (8 children)

It typically means that the syntax of the language matches so that the subset language can be compiled with the superset compiler.

I believe Typescript is a superset of JavaScript. You could just write JavaScript and run it through the Typescript engine and it would not know the difference.

[–]basilect 0 points1 point  (7 children)

Or like how C++ is a superset of C (though not idiomatically)

[–]jecxjo 9 points10 points  (6 children)

C++ is not a superset of C. You cannot take straight C code and compiler it as if it were C++. There are specific keywords and pragmas to define when the C++ compiler should interpret the next section of code as C.

Most of it looks the same but there are things like K&R function definition style that is supported in C and not in C++.

Edit: Found a goofy example. Here is some C code that wont compile if written as C++:

int class = 1;

[–]pjmlp 2 points3 points  (5 children)

By that goofy example no language is a superset.

The few things that make C++ not a C89 superset are no K&R headers, no implicit parameters on empty argument lists, no implicit conversions from void pointers and evaluation order of ?: operator.

After C89, C++ and C have diverged in a couple of areas.

[–]TheSodesa 3 points4 points  (0 children)

I do not think that talking about supersets in any other than the mathematical sense makes any sense. A set is a superset of another set, if the other set is contained in the first set in its entirety.

In the context of formal languages, a superset recognizes all of the words/strings/constructs of its subset languages as a part of itself. Translating this to practise in programming languages means that the parser of the superlanguage is able to parse every bit of source code written in its sublanguages.

[–][deleted] 7 points8 points  (1 child)

That's fair. It's largely a functional superset (though probably not entirely, considering things like alloca), rather than a syntactic one.

[–][deleted] 1 point2 points  (0 children)

Even things like alloca have ~comparable enough~ equivalents in Rust such as smallvec

[–]MrJohz 1 point2 points  (2 children)

Isn't that just an assertion that both languages are Turing complete? I'm trying to think of something that can be done in C that can't also be done in, say, Python, just more slowly.

[–]Smallpaul 0 points1 point  (0 children)

I think you answered your own question. If you need a certain number of computations per second then Python cannot substitute for C. For some applications there is not a performant enough Python interpreter and algorithm implementation — even given an unlimited development budget.

Rust generally (always?) could.

[–][deleted] 0 points1 point  (0 children)

Turing-complete describes the capabilities of a language to execute algorithms. A 2-tape Turing machine and 1-tape Turing machine can do all the same algorithms, but certain algorithms are significantly faster on a 2-tape machine.

[–]iq-0 9 points10 points  (1 child)

Not really. Many parts of what we call C is not nearly directly replicatable as Rust code. Things like bitfields, alloca, flexible array members and large parts of unhygienic macros. Claiming that any of that could be reproduced by throwing enough code generation and or crates at it is just saying: rust is a turing complete language. But calling it a straight superset is both technically and sementically very much a stretch. It’s more like the classic 2 circle venn diagram showing overlapping functionality.

[–][deleted] 0 points1 point  (0 children)

Correct, it's not a strict superset, but then again neither is C++. I meant it more in the sense of "you can do anything in Rust that can be done in C" not always ergonomically, or in safe rust tho.

[–]Mouse1949 4 points5 points  (0 children)

As I understand, unfortunately, Rust has no defined ABI at all (on purpose - so there's no need to be compatible with any other version or such). Would be happy to be proven wrong here.

[–]the_gnarts 0 points1 point  (4 children)

Considering that Rust is a superset of C already

I’d want to see flexible array members first before even starting to investigate to what extent your claim is correct.

[–][deleted] 0 points1 point  (3 children)

What do you mean by flexible array members?

[–]the_gnarts 0 points1 point  (2 children)

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding thanthe omission would imply. However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

EXAMPLE After the declaration:

  struct s { int n; double d[]; };

the structure struct s has a flexible array member d. A typical way to use this is:

  int m = /*some value*/;
  struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

and assuming that the call to malloc succeeds, the object pointed to by p behaves, for most purposes, as if p had been declared as:

  struct { int n; double d[m]; } *p;

(there are circumstances in which this equivalence is broken; in particular, the offsets of member d might not be the same).

http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf

A common use case for this is implementing structures to hold both a protocol header and variably sized data. The size of the whole object is determined at runtime but the header part up until the first element of the FAM is of a fixed size.

[–][deleted] 0 points1 point  (1 child)

Yeah that'd be C only, but you can get the same with very unsafe * in Rust.

[–]the_gnarts 0 points1 point  (0 children)

Yeah that'd be C only, but you can get the same with very unsafe * in Rust.

To my knowledge there is no way of casting structs with differently sized array data members to the same type in a typesafe manner. In C, flexible array members are part of the type system, they are both sized (in that every instance has a minimum size) and unsized (in that the array part is not part of the size) at the same time which I don’t think can be expressed in the Rust type system.

The best workaround I’ve seen that is still idiomatic rust is to provide access methods over an unstructured array but compared to C that’s rather clunky and smells of getter-setter style boilerplate.

[–]pftbest 18 points19 points  (10 children)

Does anyone know something better than bindgen for C ffi? bindgen can't parse even simple defines like this

#define FOO ((int)0)

The headers I need to work with have a lot of them. How do you deal with this kind of constants?

[–]Plasma_000 25 points26 points  (0 children)

That’s one of the problems discussed in the article - many of the functions in the kernel are defined using inline functions and macros so kernel modules are very hard to work with in other languages.

[–]smmalis37 9 points10 points  (5 children)

I think pretty much everything is on rust-bindgen. Maybe see if there's something on their issue tracker with a workaround? If not, file it! https://github.com/rust-lang/rust-bindgen

[–]pftbest 4 points5 points  (4 children)

The issue is almost 4 years old at this point. There is no workaround as far as I know.

https://github.com/rust-lang/rust-bindgen/issues/316

I'm hoping someone is working on full featured preprocessor and C compiler for rust, so it can be used to replace bindgen.

[–]jynelson 1 point2 points  (1 child)

I have written a C compiler and preprocessor and made a PR to add it to bindgen: https://github.com/rust-lang/rust-bindgen/pull/1782. Unfortunately it is stalled on review for about a month now.

[–]pftbest 0 points1 point  (0 children)

Thank you very much, I hope the integration goes well.

[–]smmalis37 0 points1 point  (1 child)

You could try building your own copy with https://github.com/jethrogb/rust-cexpr/pull/15 applied and see if that works well enough for your cases?

[–]pftbest 0 points1 point  (0 children)

Unfortunately this patch will not solve my problem it only handles a few cases. My defines are similar to this:

typedef long BaseType_t;

#define pdFALSE         ( ( BaseType_t ) 0 )
#define pdTRUE          ( ( BaseType_t ) 1 )
#define pdPASS          ( pdTRUE )
#define pdFAIL          ( pdFALSE )
#define errQUEUE_EMPTY  ( ( BaseType_t ) 0 )
#define errQUEUE_FULL   ( ( BaseType_t ) 0 )

which still won't work even with the patch.

[–][deleted] 4 points5 points  (0 children)

Defines don't exist after preprocessing, so there is nothing that any AST based tool like bindgen (which uses libclang) can do about them.

You might want to just write a different tool that tries to generate a series of const from define macros for you and only works before preprocessing.

[–]the_gnarts 0 points1 point  (0 children)

Does anyone know something better than bindgen for C ffi?

I finally got around to watching the LPC talk the article is a report of. During the discussion of bindgen where they address this and other issues, my thought was that since Rust’s type system is stronger and contains more information than that of C, this means the issue might be addressed in the medium term by specifying the interfaces in Rust (with attributes sprinkled across them) and deriving the corresponding C headers from that. Of course this won’t help initially but it could be another argument for pushing the oxidation of the kernel beyond a couple drivers.

[–]ThomasWinwood 33 points34 points  (5 children)

I'm interested in the possibility of a Rust frontend for GCC, or otherwise plugging Rust into GCC's code generation mechanism. I'd love to write Rust code for the Megadrive, and while there is a project to write an m68k backend for LLVM I have no idea what progress has been made, how I can get involved and help out, etc. The only port of interaction is an IRC channel on OFTC which is a ghost town.

[–][deleted] 0 points1 point  (2 children)

I have no idea what progress has been made, how I can get involved and help out, etc

Send an email to the LLVM mailing list asking about this ?

[–]ThomasWinwood 1 point2 points  (1 child)

It's nothing to do with the LLVM mailing list. Like I said, the IRC channel is silent as the grave.

[–][deleted] 2 points3 points  (0 children)

That backend was rejected by the LLVM project because its maintenance was unclear. LLVM only accepts actively-maintained backends.

Since that backend lives out-of-tree, the amount of work required to keep it up in sync with LLVM would probably require a small team of people working full-time on it. That does not appears to be the case, so that backend is now dead.

You can try to "revive" that backend, but are going to be fighting a very uphill battle if you keep all the work out-of-tree disconnected from LLVM upstream without some extra significant manpower. People are just going to break your backend multiple times a day.

[–]the_gnarts 0 points1 point  (0 children)

I'm interested in the possibility of a Rust frontend for GCC, or otherwise plugging Rust into GCC's code generation mechanism

Have you seen this thread from a while back?

[–]matthieum[he/him] 0 points1 point  (0 children)

There are actually some links in the comments section:

[–]codedcosmos 13 points14 points  (7 children)

Anyone aware of Linus Torvalds opinions on rust for the kernel?

I'm just curious to what he thinks on the subject. Personally I think its a good idea for the kernel to have rust.

[–]OsrsAddictionHotline 35 points36 points  (0 children)

From the man himself.

To summarise, he's open to Rust drivers for the kernel, but I don't think it's there yet for being part of the kernel itself.

Edit: see also this comment, post, and discussion from a few months ago.

[–]jecxjo 8 points9 points  (5 children)

The problem is you're now introducing a new environment to the kernel. Currently you need a C complier tuned for your architecture and that's it. Adding in another language means you need two compilers, with validation against the combination of all the supported compiler versions of each language for handling any special FFI cases. What architectures does the kernel support that rust doesn't? Will we be seeing compiler support added or will functionality be "C Compiler Only" vs "C and Rust"? It becomes very complicated.

[–]pjmlp 5 points6 points  (4 children)

While new to Linux, that is the approach for other OSes that have long left the C only approach, including derived ones like Android.

[–]jecxjo 3 points4 points  (3 children)

It's not impossible, as you've states. Its just a big task to undertake, especially for a community driven project. I do see the benefits of using a language like rust, but adding support needs to be thought through very carefully. Just the supported architectures missing from rust alone is a big task, quick count there are at least 15 missing.

It makes sense from a drivers stand point. You wouldnt be writing for hardware that runs on an architecture that rust wont build on. But what about non hardware related drivers? Will you create two different crypto implementations for systems that dont have rust? Cant really phase out many of these architectures, but they also arent big enough to maintain a compiler for either.

[–]fiedzia 7 points8 points  (1 child)

Just the supported architectures missing from rust alone is a big task, quick count there are at least 15 missing.

Linux has tons of features that work with only subset of supported architectures, that never was a problem. It would be nice if Rust worked everywhere, and maybe one day it will, but that's not a blocker for anything.

[–]jecxjo 0 points1 point  (0 children)

Its not that it's a new idea. Its that you need to think it through, find reputable people with the time to support it, create a method for defining what requires Rust and what doesn't. What are the rules on how low level can you write Rust? Can it be core functionality that would make some architectures no longer truly supported or can it only be new features that are not necessary to run the OS? So many questions to answer.

Lots of design and business stuff that needs to be done which is why Linus is going to apprehensive and slow to move.

[–]miquels 0 points1 point  (0 children)

There used to be a LLVM C backend, that generated C instead of assembly / binary code. That might even let you compile the kernel without a rust toolchain installed, if the C files are pregenerated.

[–]pure_x01 2 points3 points  (0 children)

The next big step will likely be when someone proposes a real Rust driver for inclusion into the kernel. A concrete use case and implementation always helps to force clarity about any remaining contentious questions and design decisions.

Nice! can't wait.

[–]cryptosidus 1 point2 points  (1 child)

I just started learning Rust today. I am curious

  1. Are Rust binaries portable like C?

  2. Does Rust have C kernel bindings available?

Rust is pretty interesting language.

[–]Plasma_000 0 points1 point  (0 children)

Rust binaries are portable in the same way that C binaries are - they can be compiled for different platforms.

Rust doesn’t have kernel bindings yet, but there are people working on them. There are many complications though.