Rust in 2016
blog.rust-lang.org
blog.rust-lang.org
The major criticism I came away with, due in part to the type of program I was coding, was for Rust's lack of implicit type casting (more specifically, widening). What I mean is, adding a u8 and a u16 is an error in Rust. Rust will refuse to implicitly cast the u8 to a u16. These situations came up very frequently while implementing my program because I had to do a lot of optimized, low-level math. The scattering of type casts throughout the program resulted in clutter without any obvious benefit.
When I looked into the problem, the arguments I saw against it were often explanations that Rust is meant to be explicit and non-magical. But Rust, for example, already has type inference which I classify as "magical". Implicit type widening is hardly magical. And I don't see how it would be confusing or result in bugs, as long as only safe widening is done implicitly.
I think those involved in the Rust project were just scared off from it because of C's bizarre implicit type casting rules which result in bugs for typical programmers. I can understand that, but it's not like it can't be done better in Rust. Besides, if Rust is meant to be a system programming language, won't math between differing types come up often? And wouldn't handling those cases gracefully be a boon to productiveness in Rust?
If we can reach consensus on a design, such a feature can be easily implemented. I'd love to see an RFC on the topic (https://github.com/rust-lang/rfcs)!
fn foo(x: u32) { }
let x: u16;
let y: u16;
foo(x * y);
`x` and `y` would have to be multiplied before they are widened to `u32`. This creates an overflow bug that may only be visible in the released code (release mode has arithmetic overflow checking off by default). u64 = ((u8 + u16) * u32) / u8;
It's hard to reason about what a programmer would want that statement to do. Coercing everything to u64 is the safest option. The idea, though, is to allow the programmer to use explicit casts to define exactly what they want when the need arises. So: u64 = (((u8 + u16) as u16) * u32) / u8;
Would mean u16 addition, u64 multiplication and u64 division. So you get the benefit of safe, implicit type widening without losing the ability to micro-optimize when you want to. fn required_bytes(width: u16, height: u16) -> u64 {
let size = width * height; // what's the type of size?
size + 12
} fn required_bytes(width: u16, height: u16) -> u64 {
let size: u64 = width * height;
size + 12
}
I just really don't want to have to write this all the time: fn required_bytes(width: u16, height: u16) -> u64 {
let size = (width as u64) * (height as u64);
size + 12
} let a: u16;
let b: u16;
fn f(x: u32) -> ...
f(a*b) // 32 bit result
let x = a*b; // x is u16
f(x)
Now there is a sane answer to this: define multiplication to always result in larger integer types, and require some explicit downcasting. But I'm not sure anyone will go for this. fn foo(x: u32) { }
let x: u16;
foo(x);
This way you get the 'best of both world'? int8 x = 12;
int8 y = 84;
int16 z = x + y; // ERROR: compiler is stupid int16 z = (x + y) as int16;
or int16 z = (x as int16) + (y as int16);
?byte foo = 4; byte boo = 86; byte arglebargle = foo + boo; // cast monkey! cast!
I remember old assembly language guys annoyed at the types of math available with high level languages. I think they were a bit crusty but still they had a point about inability to manage precision without big hammers.
At least the C# way forces you to think about whether you're loosing precision. But I dislike having to add casts. I use casts a lot, but I distrust the because they hide errors. I find myself really wanting safe operators and unsafe ones. Safe as in, overflow results in a hard fault that I can catch. Unsafe means overflow is silent.
The comment about assembly comes from remembering a conversation with an older firmware guy. In his world multiplying two 32 numbers resulted in a 64 bit result. And division was 64 bits divided by 32 bits with a 64 bit result, 32 bit result plus a 32 bit remainder.
I think his thoughts on C's 32 bit number X 32 number => 32 bit result can be summed up in a single word: gah!
How is that a solution to OP's problem?
class String {
int find(char needle, int startIdx);
};I ended up calling:
int idx = find(startIdx, ';');
C++ happily converted my char to an int, and my int to a char, no warning or anything. Surprisingly, the code appeared to work for months, and then when I upgrade Xcode to the beta, it started failing. Weird.I end up casting stuff when I do math, anyway, because I can never remember when 'int op float' and 'float op int' results in an int or a float, and every time I don't it ends up as a truncated int when I wanted a float in [0, 1]. So, a little more painful to have to cast stuff around, but at least you find out quickly about my find() bug and you don't end up with something like
if (secondsFromMidnightInFloat / 86400 > .5) {...}
always fails. I can't tell you how much time I've wasted tracking something like that down, despite my diligence in adding (float). That and that stupid "->" for pointers drive me nuts. Even more than header files.1: https://internals.rust-lang.org/t/implicit-widening-polymorp...
For the second case, correct me if I'm wrong, but I think both float / int and int / float have a float result. In fact, pretty much all operations involving an int and a float will have a float result.
I like static typing with inference, but I'm still getting the hang of the Rust type system. In this case, a little leniency would have been nice.
It's the perfect language for a mobile platform, and I would love to use the zero-runtime-cost abstractions without resorting to the C++ hand-grenade roulette. The fact that it has an ML heritage, with all the goodies that entails (ADTs, pattern matching, type inference, etc), is even better.
You run a single command to instantiate a family of cross-platform Cargo projects, one your platform-agnostic backend, and one for each of your target platforms: Linux, Windows, Mac, Android, iOS, of course, but also the entire impending IoT down to the smallest real-time device. Your platform-specific projects come with safe Rust bindings to the system platform (ala Xamarin).
Another single command builds and tests this from a single host system on an armada of virtual machines, emulators and cloud systems. Your tests run on a wide variety of standardized machine images, including those used for upstream Rust's own integration testing.
Another single command packages this for various Linuxes and app stores, deploys to your devices and cloud services.
Three commands, total world domination :)
With ObjC, you have similar problems and same goes for C#. It would be nice but I don't think it will happen anytime soon.
It has some nice features but at the end of the day their focus is safety above all else - this actually creeps up all over the place - requiring you to be explicit, APIs requiring you to deal with every possible error type (even the ones you don't really want to deal with), etc. Error handling was still very messy last time I checked.
You know what makes you productive with high-level (often dynamically typed) languages ? Optimistic programming - you write some code and do the least amount of work to run it and see how far it gets you. Maybe a part of it fails under some condition, maybe some default assumption turns out to be wrong, etc. ... You can live with that because you got something working in fraction of the time it would require you to handle all the edge cases and type out all the bits and peaces up front. The sooner you get to run the sooner you get to iterate.
And that's ignoring the cost to iteration time because of compile time.
Rust has it's design goals - it's trade-offs are decent for the context they are made in - but it isn't my idea of high productivity application programming language.
I admit, I have higher productivity with Scala and F#, but the productivity gap isn't insurmountable. The borrow checker isn't a huge problem once you get used to it, and that seems to be the biggest hurdle that others have. The biggest thing I find lacking in rust is a compelling asynchronous IO solution. I would love an async/await capability, or even better, something along the lines of F#'s computation expressions. Mio is making progress, but is extremely immature in comparison.
I mean you're completely ignoring the fact that Ruby lets you fire up the interpreter - write out some formulas to test if they work with some data - fix that logic and iterate over various stuff instantaneously. Consider how much time you would spend in Rust just recompiling for every iteration - not to mention dealing with a bunch of pointless typing and error handling as you're iterating over (or worse - ignoring all errors defeating their purpose in the first place).
There is making the thing correct, robust and maintainable and there is getting something to work - and despite the common theme among dev forums the latter is much more important because without it you never might get to the first part.
I'm not familiar with the term 'push-button', but Go also does cross-compilation very well.
(In fact, on Go, technically all compilations are cross-compilations; it just happens that the target platform and architecture may match the platform and architecture of the current machine.)
I'm not sure if that's still the case in 1.5 onwards, since I know they changed some stuff to make it even easier to target other platforms and architectures, but it was the case previously.
For example, it seems like compiling to MIPS is not push-button for Go. On the other hand, Rust supports all platforms that LLVM supports, though you need a version of the stdlib compiled for that platform (you don't have to write any code like in the Go case, you just have to compile the sources with the right invocation). For push-button compilation, we just need to distribute these binaries.
Given that the number of popular platforms is approximately 2 (Intel and ARM), I don't see that as a huge problem.
Also, you'd be surprised how often people request minor platforms. It's enough that we regularly get people asking for a C backend because they only have a C compiler for that target (though I think a C backend ends up usually not being what those people actually want).
In any case, just having a Rust compiler isn't very useful; you need a standard library (or at least libcore) to do anything interesting with the language, and that's where most of the porting effort comes in.
So the number of platforms is approximately {x86-32, x86-64, ARM, AArch64}×{Windows, OS X, Linux, iOS, Android} (- a few combos) ~= 17 (I think). MIPS, Sparc, and PowerPC can be useful in some scenarios, as can even x86-16 (hey, wanna write the initial startup code on x86?), and platforms like PNaCl, Emscripten, or WebAssembly are effectively their own platforms as well.
If std and core could be made into first-class crates, then you could get rid of the need to find a pre-built version of libstd for your target. It would just be built when needed by a project.
This is what we currently do in zinc for core via the hack here: https://github.com/hackndev/zinc/blob/master/Cargo.toml#L27. Zinc doesn't use std (for obvious reasons). Right now, targeting a raspberry pi is much harder than it should be as a result of having to having to find an appropriate cross-compiled std.
That is, I want the steps for cross-compilation to be:
1. Execute `cargo build --target=foo`.
2. There is no step 2.
It feels great, once one gets used with the borrow checker messages.
One thing that could make the language better(and was mentioned in the post) is faster compilation.
Having programmed in Go, this may be one of its best points, just have a watcher that recompiles the program on change(and maybe run the unittests). Though it can be argued that not all types of programs benefit from such workflow, it's still one of my favorite things.
It's certainly true that the Rust compiler does quite a lot of analysis (more than Go), e.g. non-trivial type inference and borrow checking. In fact, a no-op build of libcore takes 12s for me, with 5s in type checking, 0.8s in borrow checking and less than 3s interacting with LLVM (~1s of which are LLVM actually running). Turning on optimisations pushes the LLVM time out to 4s. Similarly, libstd takes 8s to build without optimisations and LLVM only runs for 1.5s (type checking itself is about the same).
(The plan to make the compiler more parallel and more incremental improve these parts without affecting the quality of the generated code at all.)
1. The language is designed for zero-cost abstraction, which means we have more work to do to make it compile fast. For example, any Rust compiler must solve subtyping constraints on lifetimes, regardless of the optimization level.
2. Getting an optimizing backend and a non-optimizing backend to work well is more work than just getting a non-optimizing backend to work well.
1. There are some technical issues in LLVM that prevent Rust's -O0 from being truly -O0 (LLVM's FastISel not supporting the invoke instruction being the main one here).
2. Go's compiler doesn't have much of an optimization IR at all. From what I understand, it compiles straight from the AST to Plan 9 assembly. The equivalent in Rust would be a backend that went straight from the AST to LLVM MachineInstrs (like the Baseline JIT in SpiderMonkey). Such a backend would be the ideal way to get fast whole-program compilation but would be a non-starter for optimization, so nobody has focused on it given how much work it would be. Incremental compilation would be a better use of people's time than maintaining an alternate backend, because only incremental compilation gives you algorithmic speedups (O(size of your crate) → O(size of the functions that changed since your last rebuild)).
It still takes longer to compile Rust code than many people would like. That's why it's being worked on.
I guess I'm one of those programmers who is quite alienated from systems programming - probably due to my daily work in Python / JS. The Rust lang book is quite good (great job @steveklabnik et al) but from my past experience I've found it easier to stay committed to learning a new programming language when I have a project that I can work on.
Can someone suggest a few "getting started" but useful systems programming projects that I can use as a test bed for learning Rust?
(I myself wrote a little wc for fun a month ago, still missing some compatibility things https://github.com/steveklabnik/rwc/blob/master/src/main.rs )
1- Great job. This is both innovative and powerfull. Like the idea to test nighties on every crate available on github. I am sure no other language does it.
2- So much feature may be a little disappointing. Take specialization. It may be interesting, but I don't even understand what it is. And I am not a beginner anymore ! Don't you fear that, by adding more and more feature, rust will become like the language it is aiming to replace (c++) : a huge mess of feature ?
That being said, I am definitely a rust enthusiast (I bought the book https://www.kickstarter.com/projects/1712125778/rust-program...). Carry on !
> Take specialization. It may be interesting, but I don't even
> understand what it is.
That's fair, it's hard to put a full motivation in these kinds of posts. The RFC contains the full proposal, as well as a motivations section that hopefully makes it more clear: https://github.com/rust-lang/rfcs/pull/1210TL;DR: specialization lets you also implement a more specific version of something that's generic. This lets you take advantage of this extra detail for various ends, like making a particular implementation more efficient than a general one could be.
> I would prefer if it were simpler.
If you have thoughts about how to simplify it, please get involved in that thread! We don't actively try to introduce complexity, but sometimes, features are just inherently complex. There's also the case that sometimes features are easier to use than they are to define. I think this will be one of those kinds of features. At its core it just means that if you have a Vec<i32> and you also have impl<T> Foo for Vec<T>
you'll be able to define an additional impl Foo for Vec<i32>
and your Vec<i32> will use that one instead of the more general Vec<T> one.There is no web dashboard yet (the website[1] - which runs Rust! - is quite minimal), but it's coming.
In any case, it has so many more benefits than just being actually-correct (as you say), e.g. one can edit comments/whitespace without forcing rebuilds.
I believe cargo (which calls out to rustc) is using timestamps at the moment.
Right now, the borrow checker's rules are very straightforward: references live for the entire lexical scope that they're alive. However, the downside of this is that sometimes you want the scope to end early, and so you have to code around it.
With non-lexical borrows, the rules get much more complex: borrows are determined by the control flow graph, not lexical scope. However, you no longer need to code around those edge cases.
So the question of which is better really comes down to a question: do programmers find reasoning about lexical scope or the CFG more intuitive? It would appear the latter is the case, which is kind of surprising for me, but maybe it shouldn't be.
I believe that 'borrowing part of a struct' is a different extension to the system, actually, though maybe they'll be put together. I haven't been involved in the pre-RFC myself.
I was disheartening to see what it was not a priority to tackle it. There's long standing bugs (2+ years) filed in github against it. And so far the answer has been it's hard to fix this, so we're not going to do it yet.
I'm really happy to this development. I hope this is sooner rather then later (have to wait to 2016)
I learn via technical books btw.
Does anybody know if there's a rust book coming out?
I mean Julia is having a book from Manning and they're not even version 1.
And The Rust Programming Language (https://doc.rust-lang.org/book) is on its way to paper publication.
The newly minted Rustonomicon (https://doc.rust-lang.org/nightly/nomicon/) that covers deeper aspects of Rust is hopefully destined for the same.
I appreciate that the vast majority of Rust's D&E happened in the open, so most of it's probably still available, but it's been a long and twisty road since Graydon's initial public post and picking out a coherent narrative from umpteen separate slowly-linkrotting fora would be no small task.
I did a talk at FOSDEM 2015 along these lines, which apparently got lost with a lot of them :/
My background includes OS development (Windows COM), driver development (C for kernel mode driver and C for HW firmware development), web development (C#/ASP.NET and JavaScript - both in browser and Node.js), and most recently HW simulation using C++/SystemC. Mixed bag, I know. What I want to use Rust for is desktop app development. I could go C, but that's a lot of manual labor. I could go C# but I don't like paying cost of the runtime on desktop. I could go C++ but I don't like getting my soul crushed. I tested viability of Node (and Python, actually) for desktop apps but it just didn't fit my needs.
So I want to love Rust. And I kinda do. Initially I didn't like certain things (bits of syntax mostly) but I've learned to love them. The problem is: none of the resources tell me interesting stuff all the way through the system. I feel like knowledge could be laid out with all the details from "this is syntax" to "this is what happens" to "this is what you can/cannot do" to "this is why this happens" to "this is what this means to the compiler" to "this is what happens under the hood/in asm".
I'd also[1] love to have something that roughly maps rust to C and gives me clear explanation of pros/cons of various approaches. It wasn't immediately obvious to me if I can have static methods. Of whether it's important to have self mutable (or not) as a method param. Or how to organize structures now that I have to keep mutability in mind. I know this comes with time but I'd love a kick of sorts. :) Best ideas tend to come from reading existing pieces of code but rarely knows if author is worth mimicking. :S
Either way - I'm invested and I like what I'm seeing. Great work guys!
[1] or these could become one thing, dunno
There's some things like `Option<&T>` which we guarantee but really it's a lot of "man, if the compiler was smart enough...". Even then, a stray annotation can wall LLVM and kill any chances of perf (e.g. if the function is not a candidate for cross-crate inlining).
But I don't think this is the most important thing I want to know. What I want Rust to be is the opinionated low cost, higher level language. "Do things this way, dummy!" is what I need the most (at this point of my familiarity with the language at least).
https://www.kickstarter.com/projects/1712125778/rust-program...
Any ideas which IDEs will be chosen?
Take Servo, for instance: https://github.com/servo/servo/tree/master/components or rustc itself: https://github.com/rust-lang/rust/tree/master/src
Each of these subdirectories (basically, a few aren't) is a crate, so you'll only be recompiling stuff in the subdirectory you're editing. Incremental compilation will still help with projects like this, of course.
Wonderful stuff.
I would prefer that over plugin to Eclipse or Intellij IDEA.
Still, someone can create a plugin (afaik, there was one but abandoned)
On the other hand this is a chicken and egg problem. A powerful IDE like the ones Jetbrains builds would increase Rust adoption a lot, and eventually Jetbrains would completely dominate that market.
C#'s best feature is its integration with visual studio. Rust would greatly benefit from something similar.
It's a newly stable language, these things take time.