Using Rust for an Undergraduate OS Course
rust-class.org
rust-class.org
Not those kinds of "systems". It is funny, it seems initially it was meant to be those kind of "systems", and then it pivoted as we like to say, to become a "distributed-server-network-backend systems" not "hardware-kernel-OS" kind of systems. And then creators kinds of winged it and remarked how "well, that's what we meant when we said systems".
As for Rust, yeah, there are already a few projects trying to build a kernel or drivers in Rust. Here is one for example:
From the announcement talk: "And it's a systems language in the sense that we intend it to be used to write things like web servers" http://www.youtube.com/watch?v=rKnDgT73v8s
I don't think that Google ever wanted to develop a new OS kernel.
From the announcement blog (and that was the widely circulated quote in tech news not the video transcript):
---
Go combines the development speed of working in a dynamic language like Python with the performance and safety of a compiled language like C or C++. [...] And the compiled code runs close to the speed of C [...] Go is a great language for systems programming with support for multi-processing ...
---
http://google-opensource.blogspot.com/2009/11/hey-ho-lets-go...
> I don't think that Google ever wanted to develop a new OS kernel.
As the meme goes, they keep using the word "systems", I don't think it means what they think it means.
I'm pretty sure it doesn't mean what you think it means. The only title I have on my bookshelf which has "Systems Programming" in the title is System Software: An Introduction to Systems Programming. The book is about creating assemblers and compilers for a made-up architecture called SIC: http://en.wikipedia.org/wiki/SIC/XE
This is neither here nor there, but the point is that a fair number of accomplished people in the field were apparently at one time less hung up about precisely what "systems programming" might mean and (this is just conjecture on my part, since honestly, who cares?) probably just used it as a contrast with applications programming.
These dudes were writing "systems" long before most folks here on HN were programming.
To me, and to them, "systems programming" has a particular meaning that does not include kernels. Like it means assemblers, compilers, web servers.j
I think they have more credibility about what it means than perhaps you do.
From the bit you left out:
Want to write a server with thousands of communicating threads?
It's always been clear what kind of apps Go is targeted at, and it's never been operating systems.
Yeah, I was confused at first too. Not sure how you can call it an "OS class" if students never touch kernel code. Although, apparently one project group did manage to write a kernel in rust based off of rustboot (https://github.com/wbthomason/ironkernel).
I was a TA for a year for a version of that course adapted to the school I did my PhD at. We did the same thing: this course became the required one, and OS became an elective. I was skeptical as well, but after TAing it for a year, I think it is absolutely the correct thing to do. This course instills an understanding of the whole stack of a computer system that exists in user-land: assembly; how programs are executed including the stack, data segments, dynamic memory; code generation; process management and concurrency; memory allocation; network and server programming. The point of the course is how different levels of the system interact, not just studying one component in isolation. It is the kind of course I wish I had as an undergrad; instead, I had to build that knowledge piecemeal during grad school.
More people will do user-land systems programming than kernel systems programming, so I think it makes sense to make the user-land course required, and the kernel level one an elective.
To me it means "infrastructure" - things like web servers, database servers, or the wide range of distributed systems that are currently written in Java (eg, Hadoop).
The language is quite similar to Oberon in features. You just need to provide a syscall package for bare hardware.
If powerful single user graphical OSs were written in Oberon, why not Go?
> lack of any intrinsic support for concurrency
An important part of an OS is that it implements support for concurrency - it builds processes and threads out of the raw materials such as page tables and timer interrupts. A kernel also builds its own spinlocks and higher-level waiting primitives. If you are relying on some high level language runtime to do the heavy lifting, is that really learning about how an operating system works?
For a kernel I actually see a lack of language support for concurrency as a plus. You need something to sit below the higher level stuff.
¹http://cm.bell-labs.com/who/dmr/chist.html (also published in HOPL-II) ²History of Programming Languages II, Transcript of C Presentation, p692
(Nice to see Rust trying to bring progress to the C niche, BTW.)
The other thing is that I did an OS course in C, where we got dirty playing around in the depths of the Linux kernel, replacing the scheduler, writing a filesystem, and so on. It was damn hard, but probably one of the only courses in Uni that really pushed me as a programmer generally (everything else I found easy). I think that it really helps to have experience in unsafe languages like C, just like you really need to do at least a bit of assembly when you teach computer organisation.
The lack of safety in C just means that the projects take longer to debug because the compiler doesn't catch as many mistakes; I don't think it adds much to the learning experience.
You don't need C's lack of safety everywhere for doing an OS. There were OS written in Mesa, Modula-2, Modula-3, Object Pascal, Oberon, Ada and many others.
C's strings and vectors are recipe for security exploits without any benefit for kernel programming. Or lack of type safety between compilation units.
It is a very good thing students learn C and C++ aren't the only way to write an OS. Sometimes the world seems to forget other languages have been used for the same purpose.
With languages with no GC or optional GC, calling back and forth between the new language and C is easy and direct, and so writing only new (or maybe historically problematic) pieces of a system in the new language becomes feasible.
I also don't agree with the notion that other option is "to teach students to write C code riddled with security vulnerabilities, memory leaks, and race conditions". This is exactly what such kind of course should teach them not to do, clearly pointing these problems and explaining how to deal with them. But not having exposure to that in the learning environment will only make them repeat these mistakes in the real world.
Btw, I'm a Lisp programmer and not a C fan.
Which we pay every day with extra tooling to cover for the language deficiencies on the area of security.
Personally, I found the course enjoyable and worthwhile. It was not what I was expecting, and I was admittedly disappointed that we didn't go much into actual bare-metal programming. That said, I found it to be a good introduction to the concepts of systems programming, with a few detours. As I am interested in OS coding, I'm performing my own independent study to learn the material which might have been covered in a more "traditional" OS course, but the course did a solid job of covering concurrency, memory allocation, etc.
As for the choice of Rust: I really like the language, and plan to continue using it for personal projects. I'm not sure it was quite mature enough for exclusive use this past semester (the 0.7 to 0.8 switch midway through threw the TAs for a bit of a loop), but I've been very impressed so far, and think that this coming semester of the course (which will also use Rust) should have a much better time of it. The one annoyance I had with Rust was the lack of documentation, but the Rust community (including a fair number of those in this thread) were incredibly helpful, and I'm currently working on a project to provide an alternate tutorial, using a series of small "stepping-stone" programs to build from "Hello, World" to a MapReduce implementation. Personally, I think I might have picked either Go or a blend of C and Rust for this past semester, but Rust is becoming a better choice as time proceeds.
I was also one of the members on the student team that made the ARM kernel in Rust. Due to the timing of the project (right in the middle of preparation for final exams and the rest of our end-of-semester project due dates) and some "lazily evaluated" work ethic on the part of our team, it was fairly rushed, and is much less complete and polished than I would like. I'm planning on continuing work on it. Though I would disagree with the assertion that separating Rust from its runtime is as "painless" as it has been said to be, it wasn't terribly difficult, and solutions for that particular problem are also constantly being improved.
If you are writing the operating system kernel, who is going to give you concurrency support? Does rust running on the bare metal still have "spawn" and the other concurrency primitives?
It's a pity that people rarely consider Nimrod (especially when it is far more suitable than the first two possibilities). Here is an example for anyone that is interested: https://github.com/dom96/nimkernel
Lol what? Unless tasks cannot communicate at all, or they are scheduled entirely deterministically, how can the compiler eliminate race conditions? How can a compiler even determine what is a race conditions and what is intended non-determinstic behaviour.
let value = something();
channel.send(value);
// I can no longer use `value` here, it was moved into the .send call
(There is some subtlety here; some values don't move when passed around by value, like primitive numeric types, but in general, anything that could cause a race condition (i.e. pointers) moves.)However, this isn't the end of the concurrency story; as mentioned in that article there is the `unsafe {}` escape hatch which lets you do things that could cause crashes/race conditions/etc. that the compiler wouldn't normally allow (`unsafe` is basically the programmer saying "trust me, I know what I'm doing" to the compiler).
This unsafe hatch allows one to implement shared memory with safe wrappers[1], Arc (Atomic Reference Counted) for immutable shared memory (without any locks[2]) and RWArc and MutexArc for lock-protected mutable shared memory.
[1]: http://static.rust-lang.org/doc/master/extra/arc/index.html
[2]: Rust has very good immutability support, so it can express "this value can never be modified" in the type system, so we can have shared memory without locks entirely safely, because we know that there is no modification possible and so no race conditions possible.
(I'm answering the data races question, which may be a non-sequitur if you're asking about race conditions in general, as pcwalton points out.)
"A race condition or race hazard is the behavior of an electronic or software system where the output is dependent on the sequence or timing of other uncontrollable events."
"It becomes a bug when events do not happen in the order the programmer intended."
http://en.wikipedia.org/wiki/Race_condition
If I thought that the message A would definitely get there first - who knows why but that's what I thought when I wrote the code - this is a race condition and a bug.
Please explain to me - how does the compiler prevent this race condition and bug?
(But that said, there was some experimentation a couple of years ago with "channel contracts" in Rust, which allow you to solve that problem in many scenarios by explicitly enumerating the state transitions in your channel. We have the mechanisms still in place to do this as a library if we want to.)
C++11 introduced formal support for multithreading, and defined a data race strictly as a race condition between non-atomic variables. While race conditions in general will continue to exist, a "data race" must be avoided by the programmer, who must assure that only one thread at a time may access any variable if the access is for writing.
Races related to messages are of course possible.
You can of course have races in message passing, or the disk, or the network, etc.
Data races don't mean data corruption. It simply means the result is dependent on the scheduler or something else out of your control.
Two threads set a shared variable to different values. They both lock it before the set it. But that's still a data race, as which thread write first is dependent on the scheduler.
Wikipedia: "A race condition ... is ... where the output is dependent on the sequence or timing..."
Boom! Headshot!
Non-deterministic code does not only encompass code of which the results vary with the timing of instructions relative to each other, but also code of which the results vary with the timing of instructions relative to the state of the universe or anything in it.
Boom! Headshot!
That adds nothing to the discussion and makes it feel more adversarial. It reduces the quality of discussion for no gain. Please don't do that again.Also, I might like to add, PS4 toolchain fully supports LLVM- Clang. It might be wonderful if rustc starts running on it too...
I'd recommend having a look at Erlang, it allows you to start lightweight pseudo processes for concurrency and provides a robust data sharing model.
Not so sure about low level access, we used C for those parts in the OS course I took.
I for example remember writing a kernel driver and learning about virtual memory and io schedulers. We wrote file servers in the "networking" class but hey, that was years ago and a different college, so maybe I am just old.
STM in Haskell.
[1] Not that Rust completely solves the logical memory leaks, but it makes the leaks explicit.
But you already has to think like this when you use smart pointers in C++.. "who owns this reference?" etc..
But in C++ this is a library.. not a syntax.. point to rust for this
If anyone is interested in participating in the next version of the course (which starts Jan 14), please submit the form here: http://rust-class.org/pages/spring2014.html
- "Operating systems" vs. "Systems programming"
If your definition of an operating systems course is a course where you implement your own OS or hack on the Linux kernel, this wasn't an operating systems course. But, most people have a broader interpretation of operating systems courses today, to include courses where you learn about the layers between high-level programs and physical things, and about how to build robust, scalable, and secure computing systems. This is the second type of course. Deciding do a course that was not entirely focused on building an OS was not related to the choice of using Rust (I already knew I didn't want to do a build-an-OS course before thinking about which language to use), and I don't think its controversial (this is what the majority of top programs already do). I discuss more about this in the general course wrap-up: http://rust-class.org/pages/course-wrapup.html
- Eliminating "Race Conditions"
You are right in pointing out that Rust doesn't eliminate all race conditions (and no language that allows multiple threads and any interaction with the the external world really could do this). My wording here was very careless. What Rust does is use language/compiler mechanisms to eliminate the kinds of pernicious data races (multiple threads reading and writing the same mutable state in uncontrolled ways) that are a very common and hard to find and fix problems in most multi-threaded programs.
- C's assignment operator
My perhaps somewhat hyperbolic diatribe about C using ‘=‘ for assignment is meant to illustrate how design decisions C's designers made for good reasons given the computing systems they were using in the 1960/1970s, would not be the best decisions if one was designing a language from scratch today. I think the choice of ‘=‘ illustrates this well, but there are dozens of other more serious issues in C's design that were good or necessary choices in 1972, but are undesirable legacies today: not having bounds checking, not specifying the order of evaluation for many constructs, allowing arbitrary and unchecked type casting, unsafe memory management, etc. Other languages (including Rust) that are strongly influenced by C syntax have also adopted the ‘=‘ symbol for assignment, but that doesn’t make it a good thing. For languages like Rust that are targeting experienced programmers it is probably the right choice; for languages like Python that are intended as first languages, it is really unfortunate, and many smart people who might otherwise turn out to be talented programmers are unnecessarily put-off by this. (I don't have concrete data to support this, but from having over 350,000 students in my open intro CS course that uses Python, I have plenty of anecdotal experience with students being confused by this.)
Vocational value
This was a course at a public university, so its content should not be primarily driven by immediate vocational concerns. There's nothing wrong with vocational courses, where the primary goal of the course is to improve the immediate job prospects of students who take it, but was not and should not be the goal of courses at public universities. (That said, the students entering this class are already very well qualified for the job market, and many of the 4th years in the class already have job offers, so are not in danger of not being able to get an interesting job since they only have C experience from one previous course. Knowing Rust has actually been helpful for some students in the job market, and I don't think having less C experience is a major issue for many positions.)
From a purely pragmatic viewpoint, the majority of the costs of the course are not covered from student’s tuition, but are paid by the US and Virginia taxpayers. So, our main goal should be to do what we can to enable and encourage students to do things that make the world a better place (the best ways to do that, of course, are debatable, and maybe it is learning how to hack the Linux kernel, but I think that’s a much tougher argument to make.)