A taste of Rust for C/C++ programmers
mainisusuallyafunction.blogspot.com
mainisusuallyafunction.blogspot.com
Rust is at the level of C/C++, without an elaborate run-time system. It solves the three basic problems that cause most failures in C/C++ programs: "How big is it?", "Who deletes it", and "Who locks it". It does this without throwing a garbage collector at the problem, which means it can be used for operating systems and hard real time.
The basic memory management mechanism is single-owner pointers, with language enforcement to make this work even as pointers are passed to functions. (Pointers passed to functions are "borrowed"; you can't keep a copy whose scope outlives the function return.) For more complex ownership, there are pointers with reference counts. For data shared across thread boundaries, there are atomic, locked pointers with reference counts.
We need Rust. We're still seeing CERT advisories on buffer overflows in C/C++ programs, after almost four decades of C. The threats are getting worse, too; it's not script kiddies any more, it's governments.
I just hope the Rust crowd doesn't screw up.
You're not paranoid when they're out to get you. The most recent US-CERT advisory:
Alert (TA14-300A) Phishing Campaign Linked with “Dyre” Banking Malware (Adobe Reader vulnerability) "...memory corruption vulnerabilities that could lead to code execution".
This has been going on for over thirty years. It's time for the suffering to stop.
Those of us in other systems already had better alternatives, but then UNIX was pushed into the enterprise and succeeded at that, with the outcome of thirty years of buffer overflows.
Then there was the C++ approach, trying to do it in templates. That leads to dumb subscript checking, because the compiler has no idea that the "if" involved in subscript checking is a subscript check and could potentially be hoisted out of the loop and done once at loop entry. For most math code, where this really matters, you can do one check at the top of the loop. Maybe zero checks, if the upper bound is coming from some "range" or "len" construct.
At last, smart subscript checking is coming back. This is partly because languages now usually have a "do this to all that stuff" construct ("for i in foo ..." or similar) and it's a no-brainer that you don't need to check subscripts on every iteration. The Go compiler at least has that.
How smart is the Rust compiler about this? It potentially could be very smart.
This is important. Otherwise, people will want to turn subscript checks off for "performance".
The memory management, and low level control make rust special. Thats what the author is talking about.
...why is an article that shows how to put rust into action bad?
Its a great practical article about dipping your toes into the water of using rust in my oppinion. /shrug
I know that the Rust compiler is written in Rust (step 1), but I'm not sure about the runtime.
- LLVM for the complier
- hoedown (markdown parser/renderer) for the documentation generator
- miniz for compressing the metadata stored in each library
- a few other tiny wrappers[1] for which I see no reason other than inertia/low-priority for translating into Rust (e.g. there would be no particular build-system benefit to Rustifying them because C/C++ compilers are needed for the other things anyway)
[1]: https://github.com/rust-lang/rust/tree/master/src/rt (the 'rt' = runtime name is a holdover from when the runtime was not written in Rust)
Whoa now, let's not go lumping C and C++ together! C++ has quite a heavier runtime to manage stuff like exceptions and RTTI! (though, to be honest, I don't know what's a C++ program's runtime requirement if compiled with -fno-exceptions, -fno-rtti and the like)
For example, this is the part of libsupc++ (effectively gnu's c++ "runtime") that deals with throwing: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-...
I've done it in the past with linker tricks to generate an ELF section with the list of ctor addresses. The asm that calls main (which if you're rolling your own runtime, you probably wrote as well) just calls them all right before it calls main().
C++ requires runtime support because if you remove the underlying system, it becomes stunted. Keywords like 'new' and 'delete' stop working entirely, and so a program with no imports is not guaranteed to work. Any language with a GC falls under this, obviously. Rust, if I remember right, requires restricting yourself in order to do things like kernel writing.
Nope, you just lose the standard library, and have to implement a few language intrinsics yourself.
I mean, none of Rust's _features_ don't work without runtime support, but libraries that need tasks (threads) and task unwinding require the runtime. Those are all library features not language features.
For the purposes of interrupt handling, IO ports (not all systems have memory mapped IO) and register access.
If you're going to get so specific as to call the x86 processor a runtime, then there's no point in arguing.
That external library is called a runtime in compiler design classes.
> Accessing individual registers is also a special behavior that doesn't really fall under using C, and regardless that totally possible without a runtime (the __asm__ keyword, thats all compiler driven).
The __asm__ keyword is not part of ANSI C, it is a language extension.
Not all C compilers offer support for inline assembly and in fact, a few commercial ones do not.
> If you're going to get so specific as to call the x86 processor a runtime, then there's no point in arguing.
Where did I mentioned anything like this?
Rust doesn't use a JIT, LLVM provided or not.
Of course, LLVM can be used as a JIT (e.g. what Apple is doing with javascript), but Rust does not use or need it.
Even then, assuming you're talking about FTL LLVM is "just" a codegen backend (w/ optimisations) for an existing JIT pipeline, most of the JIT infrastructure is outside LLVM.
The "thing" that rust is meant to be a good fit for is implementing Servo.
EDIT: People are excited about it because of the promise of memory-safety (fewer crashes and security issues), as a primary driver. Servo is a proof that large-scale programs can be written in this way. They're not just excited about writing things in a new language for the hell of it.
Well, PHP is forced upon me by my ISP and I never bothered to change.
Managing lifetimes is hard to reason about (though this is not a problem with Rust, but with lifetimes... Rust just makes it explicit.)
As soon as you start using generics things can quickly get out of control. Here's one of my function signatures:
pub fn xor_together<'a, 'b, 'r, A, B, R, T, U>(iter_a: T, iter_b: U) -> Map<'r, (&'a A, &'b B), R, Zip<T, U>>
where A: BitXor<B, R>,
T: Iterator<&'a A>,
U: Iterator<&'b B>
If you get those lifetimes wrong, the error propagates and you end up having lifetime errors farther in your call chain, which are quite hard to debug. I'm not even sure those lifetimes are 100% right.Some other weird things include closures. There are several types of closures, not interchangeable with each other (nor with fn) and honestly I don't understand them (and couldn't find docs to explain them). I hope this changes as Rust stabilizes.
I'm loving it so far, but boy... how hard it is.
I am no stranger to pre-1.0 languages, I have worked with lots of them, but none have been as frustrating (or interesting) as Rust... the question as to will Rust grow into a production language or an academic toy still remains an open one in my book.
Reserving judgement until 1.0 is out.
We do and we will. It's one of the reason there's such churn right now: we can't keep doing that post 1.0.
[dependencies.lazy_static]
git = "https://github.com/Kimundi/lazy-static.rs"
Does Rust, like Go, have tools for freezing dependencies? So when the git project changes, you're not left wondering why a project has sudden bugs not seen before?I love the idea of how easy these can be included in a project, but I dislike leaning on a public repository that may change at a moments notice.
For as much as I hate Maven for some reasons, I do appreciate the ecosystem and the ease at which I can...say, ask for a specific version of a specific artifact - ensuring my application will always build.
One of the main design goals for Cargo is reproducable builds. E.g. the first time anything is build Cargo will create a Cargo.lock[0] file that fixes the dependency at the exact commit that the build used, so one can come back in a year and rerun to get the same result (assuming upstream hasn't edited their history). Upgrading/changing a dep then requires explicitly calling `cargo upgrade`.
One can also manually specify versions and exact commits[1] in the Cargo.toml (the file written to specify those deps), e.g.
[dependencies.lazy_static]
git = "https://github.com/Kimundi/lazy-static.rs"
version = "1.1"
[0]: e.g. https://github.com/servo/servo/blob/master/Cargo.lock (machine generated/consumed)[1]: http://doc.crates.io/manifest.html#the-%5Bdependencies.*%5D-...
Good to see none the less!
TIL.
I'm not 100% sure if there's an official answer, exactly, but it's more representative of the state of affairs. If you do check a lockfile in, it will be ignored.
What tools are you referring to Go here? There are a handful of extra-standard mechanisms you can use to make it somewhat better (through clever hacks on urls and such), but the expectation in go-land seems to be that 'freezing your dependencies' is 'vendor them wholesale into your git repo', which is not exactly robust. Go's story on "application will always build" is kind of notoriously bad.
It would be nice to have it baked in, but thus is the life of bleeding edge development (of which I do not engage, in my safe Python Cocoon).
(git dependencies managed by the author always have the problem of the repos being renamed, vanishing, moved somewhere else)
I'm confused by this (total Rust newbie): wouldn't pass by value imply creating a new copy of the String with its own heap-allocated buffer? What does "no implicit heap allocation" refer to?
TL;DR: Modern languages can tell when you're going to be returning a value, and just place the return in the right place, rather than making a copy that'd just get thrown away. I believe this is what they're referring to.
It also might be talking about how just the tag gets copied, and not all the values. I think.
> there is no implicit heap allocation, there is explicit heap allocation when you create the String, but not when you're just moving it around
That post is about syntax extensions, so yes, you'll have to be using the nightly build if you want to keep using them. They'll be considered high priority to stabilize, but given that they rely on compiler internals... I actually argued against writing that guide because of this, but eventually said okay.
That said, yes, doing so before 1.0 takes a special kind of organization.
Rust has zero-cost FFI into C, but cannot call into C++ (without it doing an extern "c" of its own) due to lack of a stable ABI.
It has a level of flux that is astonishing (even compared to other pre-1.0 languages)... now, it could be argued that this is how a pre-1.0 should be, but it makes it very hard to build any serious project around it (note: a few companies have).
A lot of boilerplate C or C++ code was eliminated, and while we never see the actual assembly, we're told that it would generate similar instruction sets to the C C++ implementations.
So we should get pretty good performance out of it!?