The Road to Rust 1.0
blog.rust-lang.org
blog.rust-lang.org
Being one of the first to deploy a new programming language into production is scary, and keeping up with the rapid changes was painful at times, but I'm extremely impressed with the Rust team's dedication to simplifying the language. It's much easier to pick up today than it was 6 months ago.
The biggest win for us is how low-resource the compiled binaries are.
Skylight relies on running an agent that collects performance information from our customers' Rails apps (à la New Relic, if you're more familiar with that). Previously, we wrote the agent in Ruby, because it was interacting with Ruby code and we were familiar with the language.
However, Ruby's memory and CPU performance are not great, especially in long-running processes like ours.
What's awesome about Rust is that it combines low-level performance with high-level memory safety. We end up being able to do many more stack allocations, with less memory fragmentation and more predictable performance, while never having to worry about segfaults.
Put succinctly, we get the memory safety of a GCed language with the performance of a low-level language like C. Given that we need to run inside other people's processes, the combination of these guarantees is extremely powerful.
Because Rust is so low-level, and makes guarantees about how memory is laid out (unlike e.g. Go), we can build Rust code that interacts with Ruby using the Ruby C API.
I'm excited to see Rust continue to improve. Its combination of high-level expressiveness with low-level control is unique, and for many use cases where you'd previously use C or C++, I think Rust is a compelling alternative.
The design of the site is great and I'm mostly curious because it sounds like something I wish was available in the .NET world!
The short version is that the agent runs on your servers and collects information from your Rails app using the ActiveSupport::Notifications instrumentation built in to the framework. We serialize that into a protobuf that's transmitted via IPC to a background daemon (written in Rust).
That daemon batches multiple reports into a single payload that is sent to the Skylight servers, where we use Storm and Cassandra to process the requests, and periodically do aggregate roll-ups.
Unlike New Relic, Skylight gives you access to the entire distribution of response times, not just the average response time. (According to DHH, averages are "useless.")[1] This ends up being a lot of data, and a lot of CPU-intensive processing, which is why we sell it as a hosted service.
[1]: https://signalvnoise.com/posts/1836-the-problem-with-average...
https://docs.newrelic.com/docs/apm/applications-menu/feature...
No format tool yet, but its often talked about and Id be surprised if it doesnt happen.
The biggest infrastructure difference to go at the moment is that its tricky to cross compile binaries in rust.
In languages without compulsory GCs (like Rust, which doesn't have one at all) this works because it's easy to have complete control about when memory is freed, but a GC'd language may free memory that was passed back into Ruby since the GC can no longer find any references to it.
I'm sure there's ways around this (e.g. using unsafe/raw pointers), but this control is the default in Rust, and there are a pile of language mechanisms (ownership, in particular) that make making this safe much easier.
(Also, AIUI, Go's FFI story is relatively inefficient, and, has some rather bad bugs; apparently https://code.google.com/p/go/issues/detail?id=7978 is the Go GC trying to free FFI memory.)
In such a case, memory safety and GC is less of an issue.
I'd dump the part of the marketing site talking about the fast UI on Ember. It's cool that you guys use Ember and you're proud of it, but I want to see more about your product. Show me how easy it is to drop into an app, or reports you generate, alerts you flag, etc..
Ultimately, while I nerd out on what a product is built with, I only care about what it can offer me. Fast UI isn't a feature, it's an expectation.
I'm looking at your pricing, and I don't have any idea how you define the notion of a "request". Right now, I can't even begin to guess what pricing bucket my apps might fit into, and that reduces my motivation to try out your service. I speculate I'm not the only person who's felt this way.
(also, I know why you include the Ember mention at the bottom, but I really don't care. I'd rather see your awesome UI than hear what technology it's based on.)
Edit: Seriously, I'm getting downvoted for offering someone feedback on their product experience?
But isn't this stuff the job of the VM? There's no reason why a program written in Ruby can't do the same stack allocations, reduced memory fragmentation and predictable performance automatically - if the Ruby VM was better designed.
If Ruby had a better VM would you chose to use it over Rust? In Rust are you doing things that the VM could be doing for you?
Furthermore, the hypothetical "sufficiently smart VM" isn't much value for code written now.
I'm not sure I'd agree with "never", though I do agree Ruby is a hard language to optimize.
There are two challenges with optimizing Ruby: What people do and don't know the potential cost of, and what people pretty much never do, but that the compiler / VM must be prepared for.
The former includes things like "accidentally" doing things that triggers lots of copying (e.g. String#+ vs String#<< - the former creates a new String object every time); the latter includes things like overriding Fixnum#+, breaking all attempts at inlining maths, for example.
The former is a matter of education, but a lot of the benefits for many things are masked by slow VMs today that in many cases makes it pointless to care about specific methods, and an expectation not to think much about performance (incidentally, it's not that many years ago that C++ programmers tended to make the same mistakes en-masse)
The latter can be solved (there are other alternatives too), or at least substantially alleviated, in the general case by providing means of making a few small annotations in an app. E.g. we could provide a gem with methods that are no-ops for MRI but that signals to compilers that you guarantee not to ever do certain things, allowing many safeguards and fallbacks to be dropped.
Ruby's dynamic nature is an asset here in that there are many things where we can easily build a gem that provides assertions that on e.g. MRI throws an error if violated or turns into a no-op, but that on specific implementations "toggle" additional optimizations that will break if the assertions are not met. E.g. optional type assertions to help guide the optimizer for subsets of the app.
In other words: how optimized Ruby implementations we get depends entirely on whether people start trying to use Ruby for things where they badly need them.
Escape analysis falls down pretty regularly. No escape analysis I know of can dynamically infer the kinds of properties that Rust lets you state to the compiler (for example, returning structures on the heap that point to structures on the stack in an interprocedural fashion).
This is not in any way meant to imply that garbage collection or escape analysis are bad, only that they have limitations.
On the other hand, Rust is still able to perform its kind of escape analysis (via lifetime-tracking type system), because the relevant facts are embedded in type signatures of Rust functions, and as such must be present even for separate compilation (even if the actual implementation of the function is unknown).
In any case, without the type system and compiler to enforce the discipline the programmer is going to lose a lot of control and predictability.
Why? Because in theory, a VM should be able to do as well, if not better, than a programer.
The limit of a VM is that you are extremely limited in the amount of resources you can allocate to the VM to determine how much resources should be freed. See what I mean?
In the end the VM is nothing less than a program. With RAII, for example, there is zero overhead associated with the decision of releasing a memory block, because it's compiled into the program.
On top of that if you start to add some nasty memory optimization tricks allow you, you start to get why manually managing memory is still very interesting when performance and/or memory usage is important.
In my view the whole problem comes down to whether or not you are using an idiomatic approach or not. In idiomatic C#/Java you use a lot of heap allocations, garbage collection, you may be using dynamic dispatch and so on.
If you write a C# program that uses stack allocation only (no classes, only structs and primitives), no inheritance/polymorphism, no exceptions, you should find that the CLR stands up pretty well to a C++ program. Sadly, what you have done then is essentially code in a very small subset of C#, OR you have achieved something that is so hard and so prone to foot-shooting you could just as well have used C++ to begin with.
To reverse the argument: if you use C++ with loads of garbage collected objects etc. you will end up with performance similar to a java/C# program. But in idiomatic C++, you usually don't.
The small (steady state) performance difference remaining when doing the "exact same thing" in both programs is just down to how good the C++ compiler is vs. the VM JIT at optimizing (usually better, sadly).
What intrigues me about Rust is that hopefully we won't have to choose between readability and elegance vs. performance and safety. Keep up the good work.
That's not saying that C# can't be made more efficient by avoiding allocation in performance critical sections, but overall it's going to perform worse than C++, both idiomatic and non idiomatic versions. It's just that for most use cases C# is used the performance difference isn't relevant anyway.
Java doesn't even have value types, so it can't even come close in terms of memory layout efficiency and avoiding allocations without perverse levels of manual data structure expansions - for eg. consider how would you do the equivalent of : struct small { int foo; float bar; char baz; }, std::vector<small>.
The option to using "straight jacket C#" is using C/C++ interop for a part of the program. If that part is large enough, such as in most high end games, it's usually worth biting the bullet and go C++ throughout. Luckily again, those programs are rare.
Point is still that C-style C# is almost as fast as C++ (modulo compiler specific optimizations) but for the reasons above, that fact isn't very interesting.
What gives is this. The people who right these fast C/C++ programs that beat Java/C# are usally far more skilled and trained.
Any programmer who know neither language well and has to write a big application in it will probebly find java/c# far easier.
I remember a long blogpost where somebody set out to test this on himself. And that guy was a very good C++ programmer. He found that his C++ prgrogrammer was slower, but he then set on improving the speed and in the end beat java by quite a bit. However the amount of effort was completly unreasonable for most programmers.
So "What gives" is this, and this has been true for a long time. If you are a expert in a low level language and spend time optimizing you will probebly beat Java/C#.
I would suggest that you should look into what a JIT or GC can and can not do. Some of the performance problems you identivy are really almost never a bottleneck anymore.
I urge other folks here to not merely blindly downvote posts they disagree with, reserve downvoting for posts that don't deserve to be seen at all because they don't contribute to the discussion.
- C when I absolutely had to (kernel/modules/plumbing).
- Python for scripting and broad accessibility.
- Haskell when I had the choice and I knew everybody who would work on the project.
I was skeptical of Rust when it first came out, due in large part to the many different kinds of pointers it originally had, many of which involved significant manual memory management. But now, with a strong static type system, garbage collection, pattern matching, associated types, and many other features, Rust is looking like a serious contender to replace all three of those languages for me.
Still waiting to see if it develops a strong following, community, and batteries-included library ecosystem, but I need to start doing more experiments with Rust.
Disappointing to see yet another language-specific package management system (Cargo), though.
(We have previously said "opt-in GC" but that was a lie. See https://news.ycombinator.com/item?id=8312327 for more.)
If anyone is interested, I think it could be interesting to see if the refcounting approach I developed for handling cyclic references in my library "upb" would work as a Rust library:
https://github.com/haberman/upb/blob/master/upb/refcounted.h
The basic idea is that you refcount groups of objects instead of refcounting objects independently. You compute the groups dynamically such that no cycle can span groups. This is sort of like an arena, except that no arena is ever explicitly created, and the collection can be more precise than an arena.
In my library, objects go through a two-phase lifecycle. First they are mutable, then they are frozen, after which no further mutations can be made. When an object is frozen, its outbound pointers are also frozen.
The nice property of this scheme is that you can perfectly compute these "virtual arenas" by computing strongly-connected components at freeze time. This makes the scheme optimal for frozen objects. For mutable objects, the groups are computed more conservatively and objects may not get freed as often as they otherwise would with a perfect scheme (ie. the same downside of an arena).
I would love to know how this scheme might possibly be modeled in Rust. Since Rust also has strong semantics around mutability and immutability, it seems possible that a very nice idiomatic Rust interface could be implemented around this scheme, giving more precise cleanup than an arena while still allowing circular structures.
OpenSUSE's Open Build System would be great to ship independent packages, but those are again heavily tied to Unices, hence leaving other platforms behind.
As far as I can tell, one of the main justifications for most language package management systems is "we also run on Windows/OSX, which has no package management, so we'll invent our own". As a result, users of systems that do have sane package management get stuck with multiple package management systems, one for the distro and one for every language. Even then, I find it disappointing that nobody has built a cross-platform package management system for arbitrary languages to unify those efforts.
And you should add Linux to your Windows/OSX as being an issue, which Linux package management tool would you build packages for? All of them?
The end user package management provided by the OS should be for installing end user packages and the language tool for installing and publishing libraries and dev tools.
Precisely so.
When languages try to hook into existing OS-level systems, the people on the language end get frustrated by the way the people on the package-manager end don't hurry to rush out bleeding-edge versions of packages the second they hit Github. To the package-manager people, that's no big deal, their orientation towards stability and predictability makes them comfortable with waiting a little for the coffee to cool. But to the developers, who want to get their hands on the Latest and Greatest Right Now!, it feels like slogging though molasses.
So the developers eventually end up blowing their stacks and stomping off yelling "Yeah? Well fine, we'll build our own package manager then! With blackjack! And hookers!"
But just because I'm doing that doesn't necessarily mean I want, say, the latest unstable version of the window manager.
Given the diversity of OS in the IT landscape, which systems are those?
The only existing system that I might trust enough for this kind of thing Nix, and at least for Haskell it is a pretty solid alternative to their language-standard package manager. Unfortunately, its popularity is way lower then stuff like apt or yum which are pretty shitty for development compared to something like pip or cabal.
It's been a huge step up from makefiles in developing Servo. I can barely ever bring myself to go back to using make and git submodules now.
The workflow of breaking up your project into small, self-contained packages that people hack on independently and are all built with a package manager that natively understands the language and can build without a single line of shell is a huge improvement over vendoring dependencies and dealing with a maze of configure scripts or arcane make/(insert your preferred replacement here) files. It improved compilation time a lot too!
As a maintainer of a number of Rust libraries I highly agree! It has definitely won over a few skeptical collaborators of mine (who were fans of Make).
Coming from Python I find Cargo very very smart and very well thought so far, it is not feature heavy, but everything has a very clear and useful purpose. For instance today I found that if I created a file .cargo/config I could override my dependancies to make Cargo search projects on my fs instead of grabbing them on Github, while doing developments it's a big thing I think.
I don't think it is. You need support for Rust modules on various platforms Linux/Mac/Windows(possibly Android). No single tool works on all those platforms. Cargo does and it has minimal dependencies.
Not having to juggle three different configuration (CMake, Makefile, etc.) on different platform is actually pretty great.
So what is the solution to have portable packages for:
- RPM systems
- Debian systems
- tarball systems
- Pkg systems
- MSI systems
- Mainframe OS
- Embedded OS
- Aix/HP-UX/Solaris package systems
- ...
Previously, I wrote shell scripts and worried whether everyone on the team had rsync installed, or xmlstarlet, or some other less common tool. Now I wrap those scripts in a Nix package that explicitly depends on all those and distribute with confidence. It's fantastic.
Bundler and rubygems, for example, do various things that make good support within Nix rough. Two examples: 1. rubygems has no standard way of declaring dependencies on C libraries; 2. as far as I know there is no way to ask Bundler to resolve dependencies, create a Gemfile.lock, but not install any gems (I realize github gems must be downloaded to see the gemspec...)
That said, the reason that you want it to do the installation is that a lockfile is supposed to represent the way to do a build successfully. Without building everything, you can't actually be sure that the lockfile is correct. In theory, it should be...
Sure, and I'd like to do that build within Nix (and someone else might want to do it with another packager), which gives a stronger guarantee than Bundler since it incorporates C library dependencies and more. Anyway, the specifics aren't relevant to this discussion, and it seems you have a grasp of the issues, so carry on!
The good news is that you can integrate your language-specific tools with Nix as well, such as has been done for Haskell, node.js and other things. (I'm looking at it so that we can integrate our Dylan stuff with it.)
But the world of operating systems is so much bigger than the desktop under the desk.
Good work on Dylan by the way.
Maybe unikernels like OpenMirage will help make things interesting.
And thanks! The work on Dylan is a lot of fun and keeps me semi-sane by keeping me busy.
- Rust has a distributed-by-default documentation generator (rustdoc), cargo knows this and provides `cargo doc` to render a library's docs with it.
- rustdoc can run code examples in the documentation as tests, to check that everything is up-to-date, `cargo test` does this (along with running the in-source unit tests and any external tests).
- the Rust compiler allows for plugins, which are dynamic libraries loaded into the compiler and can be used for things like custom macros (aka procedural macros aka syntax extensions) and custom compiler warnings. cargo understands these, allows them to be 'imported' via the normal dependency mechanism, and specifying `plugin = true` in a package makes cargo do the right thing, e.g. building as a dynamic library (static libraries are the default) and compiling for the correct target when cross-compiling.
I'm sure all of this is possible with other systems, but it seems unlikely to be so nice to use.
Other systems can get you much of the way there (node, Python are the only ones I'm really familiar with) but I suspect you need a little language help to achieve the same kind of convenience.
No, I don't want my language to be bound to someone else's package manager at all.
As a packager in a Linux distro, I'm disappointed every time somebody tries to cram in PL-specific packages inside distro packages.
That's a much bigger problem to tackle, and one that nobody has really thought much on or done much work with. Package management is a tough problem in the abstract, and it's a big challenge to create a meta-package management infrastructure, more so when there doesn't seem to be any money in it (at least not easily).
But Rust stands out because the rest of the language is such a joy to use, compared to pretty much any other 'systems' language out there.
Congratulations to the team!
Rust starts with ownership, but makes it easy to ergonomically and safely lend out that ownership (including one-at-a-time mutable leases) of both stack and heap allocated pointers.
I've been programming with Rust since last December, and I have had essentially zero segfaults coming from Rust code during that time frame, roughly equivalent to what I would have expected writing code in a language whose safety guarantees come with a runtime cost (GC or ARC).
Almost sounds like they borrowed this thinking from Exokernel design... I think Rust is shaping up to be a very exciting language.
I just wish Rust was a bit less verbose. Requiring, for instance, type annotations on function arguments because it's sometimes helpful is such a weird decision. Let the programmer decide when an annotation is needed. This gets annoying when you get into functions with complex arguments. Especially for local functions where the signature could be messy, but the limited scope means annotations just clutter things. I'm not sure why Rust forces us to compromise here.
> This gets annoying when you get into functions with complex arguments.
Have you seen the where clauses yet? This should significantly help complex function declarations.
Comparing C# and F#, the extra annotations change the frequency in which I'll introduce an inner (local) function. For instance, here's a little helper in a piece of code I'm writing at the moment. It's part of a larger function and isn't exposed beyond 5 lines of code.
let runWithLog f name =
try run f name with ex -> logExn ex
Used in:
runWithLog "sessions" collectSessionStats
runWithLog "visitors" collectVisitorStatsHaving to add "(f: string -> RunParams -> Whatever -> unit) (name: string)" almost doubles runWithLog helper yet provides no benefit. And this an extremely simple case! Once the arguments are generic, higher-order functions themselves, it gets quite noisy.
Sure, if it's a top-level export, then maybe annotating is a good idea. But if it's limited in scope then what's the harm?
Not that it'll change when I use Rust - there's nothing competing in this category. It'd just be nice if the language let the user decide on the style.
fn fubar(x: uint) {
let times = |n| x * n;
println!("{} * 5 = {}", x, times(5));
}
Rust only enforces type annotations on top-level functions.> Does the compiler gain a large benefit from not having to include this feature? Who loses by allowing users to do what they want?
FWIW, I feel precisely the opposite as you. I'd rather have an ecosystem of code where top level functions must be annotated by types than an ecosystem where types are there only if the author feels like adding them. There is a small cost but a large gain, IMO.
Can top-level definitions be of the lambda form? If not, what's the reason to have separate ways?
You can actually define `fn`'s within other `fn`'s, but their types are not inferred. Closures cannot be defined at the top level, presumably because there is no global environment to capture. (Rust does have global "static" constants, though, which are available everywhere in the enclosing module I think.)
I can probably hazard a few guesses at why there is a split here (probably relating to lifetimes), but I don't know for sure. Perhaps someone else will chime in.
Personally I like seeing types and I'm glad people are forced to write them. (This is an opinion I've had long before Rust existed, so I'm not rationalizing excuses.)
Everyone else who looks at your code who would have preferred them.
I dabble with both Go and Haskell, and this seems like the best of both worlds: from Go they enforce a uniform standard across libraries, coworkers' code, etc, and from Haskell, they're adopting the philosophy that "types are documentation".
I, too, would be a little annoyed at documenting lambdas, like this, but I think it's eminently reasonable to require it for all top-level function definitions. And it sounds like from another comment here, that that's the case. :)
Haskell and other extremely strongly typed languages can infer the types of function parameters, yet the community still agrees it is good practice to annotate your work.
Also, the type annotations can be added on later. While you work and play with ideas, leave everything unannotated. After it's cemented and perhaps refactored a bit, add the "contract". In Rust, even while working things out, the user has to figure out and jot down the types.
Unequivocally, yes. My logic is that, while writing the function may be slightly quicker and more convenient if you can leave off the type, reading that same code is made at least an order of magnitude easier if the type annotation is sitting there in the code.
Actually, it gets better than that. Writing down the type of a function before writing the function often helps you write the function.
Protip: Use `ghc` with `-fwarn-missing-signatures -Werror`, or even better, `-Wall -Werror`. :-)
Now in some ways this may seem silly - are you really going to understand code without understanding types? But especially for people new to the language, _or_ when you're dealing with new libraries (if you've ever written a little wrapper around a function from a complicated library, you know what I mean), it's nice to choose whether you want to work from values or from types (where undefined is your friend).
Which isn't to say that think rust's decision is bad, just that having flexibility makes this kind of tooling easier (and I'm assuming here that in all cases, the end result will be all annotated top level functions). And, part of rust's choice was probably to make type checking easier, which is an important thing (especially given how sophisticated the borrow checker is).
The `undefined` trick is also immensely useful. I use it a lot when starting a new module. Rust also has a notion of bottom, indicated by `!`, which will unify with all types. I frequently use this in Rust in a similar way that I use `undefined` in Haskell. (In Rust, you would speak `fail!()` or `unreachable!()` or `unimplemented!()` or define-your-own.)
> (if you've ever written a little wrapper around a function from a complicated library, you know what I mean)
Yes, absolutely. I haven't really run into this problem with Rust yet though. Types are generally pretty simple. If and when Rust gets higher-kinded types, that would assuredly change.
fn diverging() -> ! {
unreachable!()
}
The two `!` in that code are completely orthogonal things. See the manual on diverging functions: http://doc.rust-lang.org/rust.html#diverging-functionsNot at all!
Typing should be a conversation with the compiler. If you have a strong understanding of what you are writing then, yes, writing the types first makes sense. On the other hand, sometimes I only understand how some particular pieces fit together—at this point, I want the compiler to throw its inference engine at my code fragment and tell me everything it can!
Typing and programming is exactly the same as theorem stating and proving in mathematics. It would be idiotic to have one-way information flow only.
That said, it's also practically criminal to just hand someone a proof without stating what you think it's supposed to be proving. Ultimately, that it where you must wind up.
OTOH, it can be better for exploratory coding in some circusmtances not to. (For one thing, it can be a tool to find cases where you accidentally write something that is more general than the types you were thinking of, but perfectly valid for the more general type -- which, at least as someone fairly new to Haskell, I find myself doing a lot.)
In separate compilation you have to annotate functions anyway, and in most large ML projects I've worked on people tend to annotate just because the error messages get much better. This is a common design in functional languages these days.
How does Rust compare with C++11 as a language? C++11 seems to (in some ways) have caught up with what Rust has to offer (compared to older C++ versions) e.g. smart pointers, concurrency and regexes part of the standard library
Steve Klabnik has been doing a ton of work on the docs, and that has brought a lot of "coming from an HLL" perspective to them.
Also, I'm coming mainly from a C background, not higher-level language.
Rust is inherently memory safe - however in practical terms this isn't important for most applications. If you are writing security critical applications Rust will provide you with some very important guarantees (ie. there are certain mistakes which are inherently not possible in the language). C++ doesn't really guarantee anything and if you're an idiot you can shoot yourself in the face. However in practical terms memory management in C++11 is very straightforwards and C++11 compliant code (ie. using the STL and not writing it like C) is very safe and clean. You're not mucking with raw pointers anymore
The main issue I see is that Rust is still in early development. It may or may not get "big" in the coming years. And library support is ... lacking
In contrast C++ has the STL and boost and every library under the sun. I haven't working with a lot of other languages extensively, but I've never seen anything as clean, robust and thorough as the STL and boost. C++ will remain relevant for a long long time. If Rust takes off in a big way, you'll be well positioned to jump ship.
I think (coming from a dynamic language world) the memory safeness is what pulls me towards Rust. But from what you say and what I've read elsewhere, that was old-style C++ and not C++1[17].
Thanks!
My advice is, if you're learning it for work, then go with C++. Even if it succeeds, it will take some years for Rust to be mainstream and as pointed out the library support is great.
If you're learning it for fun or for the sake of learning something new. Then Rust is a very nice and promising language bringing things from functional languages that C++ lacks and offering very interesting tooling around it.
Whatever you choose, after you feel confident with one go and learn the other as it will probably give a better perspective in the strengths and/or weaknesses of both.
However, I cannot agree with you that Rust's safety guarantees are not useful for most C++ programs, or that you have to be an "idiot" to do memory-unsafe things in C++11. Someone at Yandex recently did a presentation about Rust [1] in which they pointed to a bit of (completely idiomatic!) C++11 code that caused undefined behavior. The audience, full of seasoned C++ and Java developers, was asked to identify the problem. Not one of them could point to what was causing it (the compiler certainly didn't). The next slide demonstrated how Rust's compiler statically prevented this issue. The issue could have taken weeks to surface and days to track down, and the C++ compiler simply didn't have enough information to determine that it was a problem. This is something that happens over and over to anyone using C++, in any application, not just security-critical ones.
I'm not saying C++11 doesn't improve the situation, because it does--it would be disingenuous to say otherwise. But it's equally disingenuous to imply that C++11 makes memory management straightforward or safe. It does not.
[1] http://habrahabr.ru/company/yandex/blog/235789/ (note: the presentation and site are in Russian).
It would be great to have at least that segment of the talk translated. Sounds like a good example.
const regex r(R"(meow(\d+)\.txt)");
smatch m;
if (regex_match(dir_iter->path().filename().string(), m, r)) {
DoSomethingWith(m[1]);
}
- What's wrong with this code? - Haqrsvarq orunivbe va P++11
- Pbzcvyre reebe va P++14
- .fgevat() ergheaf n grzcbenel fgq::fgevat
- z[1] pbagnvaf vgrengbef gb n qrfgeblrq grzcbenel
(http://rot13.com/ 'd if you want to guess.) std::string get_url() {
return "http://yandex.ru";
}
string_view get_scheme_from_url(string_view url) {
unsigned colon = url.find(':');
return url.substr(0, colon);
}
int main() {
auto scheme = get_scheme_from_url(get_url());
std::cout << scheme << "n";
return 0;
}If I have a string like "foo bar baz" and I want the second word, should I copy out that data into a whole new string? That seems rather inefficient.
(How is a compiler going to optimise that away?)
Keep in mind that on a 64-bit architecture a view is at least 16 bytes large and that small strings can be copied to the stack resulting in better locality and reduced memory usage.
Last but not least, with copy elision, your temporaries might not even exist in the first place.
Example:
std::string data;
// ...
auto str = data.substr(2, 3);
// pretty sure str will be optimized away
if (str[0] == 'a') std::string str = data.substr(2, 3);
return str;
to only allocate one new string (for the return value of substr), instead of two. There's no way the compiler can get out of constructing at least one std::string for the return value, especially if there's any form of dynamic substr'ing (e.g. parsing a CSV file with columns that aren't all the same width).Sharing is only multithreading unfriendly if there's modification happening, and modification of textual (i.e. Unicode) data is bad practice and hard to get right, since all Unicode encodings are variable width (yes, even UTF-32, it is a variable width encoding of visible characters).
Furthermore, a string_view is strictly better than a string for many applications, since a string_view can always be copied into a string by the caller if necessary (i.e. each function can choose to return the most sensible/most performant thing, which is a string_view if it's just a substring of one of the arguments).
The only sensible argument against string_view in C++ I know is: it's easy to get dangling references. Which is correct, but that's a general problem with C++ itself, not with the idea of string views (Rust has a perfectly safe version in the form of &str, which cannot become dangling like in C++).
> Keep in mind that on a 64-bit architecture a view is at least 16 bytes large and that small strings can be copied to the stack resulting in better locality and reduced memory usage.
No, a string_view points into memory that already exists, there's no increased memory usage; a small string copied on to the stack will be part of the string struct, which is at least 3 * 8 = 24 bytes: a pointer, the length and the capacity. Also, a memcpy out of the original string is always going to be more expensive than just getting the pointer/length (or pair of pointers) for a string_view, since the memcpy has to do this anyway.
[1]: http://en.wikipedia.org/wiki/Copy_elision
[2]: http://definedbehavior.blogspot.com/2011/08/value-semantics-...
Sharing is only multithreading unfriendly if there's modification happening, modification of textual (i.e. Unicode) data is bad practice and hard to get right
Read-only access to data indeed scales "infinitely" on modern architectures.
No, a string_view points into memory that already exists,
Yes. Right. How do you store that? You need at least one pointer and and an int or two pointers. That 16 bytes. Memcpy for a couple of bytes is very quick when it's stack to stack thanks to page locality.
Also, if you are using pointers you will have aliasing issues which will have an impact on performance. If you work by values you allow the compiler to optimize things better.
For small strings string view are just dumb and "most of the time" strings are very small.
To give a better example of why working a string view is both a bad idea and dangerous, it's as if you said "I don't want to copy this vector, therefore I will work on iterators". That's obviously a bad idea.
Not just sucked; it was entirely wrong. Copy elision is not related to std::string vs. string_view. Even with copy elision turned up to 11, returning a std::string will be more expensive than a string_view.
> How do you store that? You need at least one pointer and and an int or two pointers. That 16 bytes. Memcpy for a couple of bytes is very quick when it's stack to stack thanks to page locality.
I was very careful to cover exactly this in my comment.
Computing the memcpy is strictly more work than creating a string_view, since you need the information that is stored in a string view (i.e. pointer and length) to call memcpy.
Furthermore, the 'stack string' is actually stored contained inside a std::string value, which is larger than 16 bytes. There is no way that returning a string_view causes higher memory use at the call site than returning a std::string. (If you're complaining that it forces old strings to be kept around, well, you can always copy a string_view to a new std::string if you need to, i.e. a string_view can do the expensive 'upgrade' option on demand.)
Here's the quote from my comment above:
> a small string copied on to the stack will be part of the string struct, which is at least 3 * 8 = 24 bytes: a pointer, the length and the capacity. Also, a memcpy out of the original string is always going to be more expensive than just getting the pointer/length (or pair of pointers) for a string_view, since the memcpy has to do this anyway.
> Also, if you are using pointers you will have aliasing issues which will have an impact on performance. If you work by values you allow the compiler to optimize things better.
You do realise that a std::string contains pointers and so on inside it? Furthermore, the small string optimisation (copying to the stack) means every data access to a std::string includes an extra branch.
> For small strings string view are just dumb and "most of the time" strings are very small.
So instead of just having a cheap reference into a string you're happy with the overhead of a function call (memcpy) and a pile of dynamic branches? I wouldn't be surprised if the branches are the major performance burden for std::string-based code that is processing a pile of substrings of some parent string. In this case, the data from the string_views will normally be in cache anyway (i.e. it will've been recently read by the function that decides who to slice into the string_view).
> To give a better example of why working a string view is both a bad idea and dangerous, it's as if you said "I don't want to copy this vector, therefore I will work on iterators". That's obviously a bad idea.
It's not obviously bad to me. In fact, it seems very reasonable to work with iterators rather than copying vectors (isn't that exactly what the algorithm header does?).
If your problem is that it is unsafe and hard to avoid dangling pointers etc, that's just a fundamental problem of C++ and is unavoidable in that language. One fix would be to use Rust; it handles iterators and string_views safely.
- iterator invalidation
- dangling references
- buffer overruns
- use after move (and somewhat, use after free)
- general undefined behaviour (e.g. overlong shifts, signed integer overflow)
And there's more to memory safety than security critical applications. Rust means you spend a little more time fighting the compiler, but a lot less time fighting the debugger and a lot less time trying to reproduce heisenbugs caused by data races/undefined behaviour.Of course, the library/tool support is indisputably in C++'s favour.
> if you're an idiot you can shoot yourself in the face
If you're a human you will shoot yourself in the face. It just takes far too much brain power to write correct C++ always (a single mistake leads to brokenness), especially in a team where there can be implicit knowledge about how an API works/should work that may not be correctly transferred between people.
On the other hand, it's somewhat ironic that you point to overlong shifts as a C++ problem when Rust has the exact same behavior. What does this function return?
pub fn f(x: uint) -> uint { x >> 32 }
Honestly, I loved the idea of Rust. I was sold a memory-safe C++, and that sounded awesome. But what I got instead was an ML with better low-level support; it felt like an enormous bait-and-switch, as nobody is interested in yet-another-functional-language.> use-after-move [...] ---references outlasting their owner---
Not really, e.g.
std::unique_ptr<int> x(1);
foo(std::move(x));
std::cout << *x; // undefined behaviour
Unless you mean something other than `&` references.> Honestly, I loved the idea of Rust. I was sold a memory-safe C++, and that sounded awesome. But what I got instead was an ML with some low-level extensions; it felt like an enormous bait-and-switch, as nobody is interested in yet-another-functional-language.
Something in this sentence has to be wrong, since people are clearly interested in Rust: either people are interested in YAFL or Rust isn't what you seem to think it is.
Anyway, that just sounds like a 'problem' with your background/expectations and/or whoever sold it to you. Rust is a C++ competitor (i.e. targets the similar low-level space) but it is not definitely trying to just be a C++ rewrite fixing the holes. I don't think there's any official marketing implying the latter.
In your unique_ptr example, you're right: the reference doesn't outlast its owner, but it becomes a dangerous zombie after getting its guts removed. It is worth mentioning that the behavior may or may not be UB depending on how `foo` takes its parameters: std::move is really just a cast.
Maybe interest is the wrong word to use; many functional languages have generated a lot of interest, but this interest has historically not translated into actual mass usage. Instead, popular languages have adopted certain functional features over time (lambdas, comprehensions, type classes, etc), but have remained fundamentally Algolian for the most part. Rust seems to go in the opposite direction: start with ML (or something ML-like, anyway), and strip it down until it fits into the C++ space.
I am definitely interested in C++ replacements, to be clear. I have explored things that stray from it much more than Rust, such as Haskell and ATS, but I went into those fully expecting to see something different. But look at documents such as [1], and tell me that it doesn't create the expectation that Rust is trying to fit C++'s shoes a little too tightly. Additionally, trawling through mailing list discussions, familiarity with C-like languages seems to have been a design principle since the start (see for example the <> vs [] for generics debate).
Finally, I wasn't (and am not) passing judgment on Rust for being what it is. I was conveying my experience from being excited about it, to being less excited about it after actually learning it. I don't expect a productive discussion to come out of it; I've also seen how defensive the Rust community can be [2].
[1] https://github.com/rust-lang/rust/wiki/Rust-for-CXX-programm...
[2] https://pay.reddit.com/r/rust/comments/2bbeqe/it_started_out...
On that note, would you interpret [a] as meaning Rust is trying to be a functional language? The reality is more plagiaristic: functional language have nice features and so Rust borrows some of them. (In my mind the correct interpretation of both documents would be: Rust is a mesh of various languages with enough similarity to many for translation guides to be helpful.)
There has been syntactic decisions tilting towards C++/Java/C# programmers (like the <> for generics), but as far as I can remember those sort of decisions are all minor in terms of semantics. For the most part the actual semantic behaviours are considered in terms of "does Rust need this" rather than "will this move us more towards C++" (even if the feature was inspired by C++).
[a]: http://science.raphael.poss.name/rust-for-functional-program...
I must thank you for pointing that link out to me, though: it said what I was trying to say much better than I could in its prologue. Namely, how hard it is to sell a functional language to old-school C people, and how Rust may have a hard time with that (even if it's not a pure functional language).
Rust is not functional. It may draw heavy inspiration from statically typed FP, and closures, ADTs, pattern matching, and and expression-heavy programming style might give that impression, but it is at its heart a procedural systems language. As stated in the blog post, most of Rust's core features map directly to underlying machine instructions, and there is always the opportunity to cede control from the type system if you absolutely have to. Indeed, core library types like `Box<T>` and `Vec<T>` are at their fundamentally built on `unsafe` code.
If anything, it's the functional parts that feel bolted on: closures are crippled (though getting better soonish), the types that get spit out of iterator chains are hideous, no currying, no HKTs, functional data structures are much harder to write w/o a gc, etc.
Leaving aside whether I think that's a fair description of Rust, I think plenty of people are interested in a functional programming language without the overhead of GC that is suitable for use as a low-level systems language.
You probably shouldn't write "Nobody is interested..." when what you really mean is just "I am not interested..."
The Rust compiler is vastly smarter and gets you type checking you have to pay out the nose for in C & C++. I'm a fanboy of Rust, but I would suggest looking hard at Rust for any C or C++ production code going forward. (My default going forward for this space will be Rust unless overriding considerations say otherwise).
I haven't touched Rust since 0.7 (where I abandoned it after filing two bugs on the project before I even got argument parsing working), but it's the most promising of the new batch of languages in my mind, and I have some side projects in mind I'd like to try when 1.0 comes out.
I only ever looked at Rust from a 500 foot view while toying with it at a Hackathon, but I had no clue it had so many different types of threading models. This seems like a step in the right direction, indeed. If Task is going to your unit of concurrent execution, as much transparency around that as possible is a good thing.
By default, yes.
Starting with green threads is really easy too:
extern crate green;
extern crate rustuv;
#[start]
fn start(argc: int, argv: *const *const u8) -> int {
green::start(argc, argv, rustuv::event_loop, main)
}
fn main() {
// this code is running in a pool of schedulers all powered by libuv
}
Edit:
You can also start up a new libgreen scheduler pool at any time and add some native threads to the pool. So you can have some threads running with libgreen and some with libnative. (So you could theoretically embed a go program inside a rust program)For a language to take off, it badly needs a very good (ideally "official") development experience, such as a custom eclipse impl, or a very good IntelliJ plugin. When a dev experience comes with batteries included it lowers the treshold substantially from just "use whatever text editor you like and compile on command line, here is a readme".
This would be amazing. One of the big issues blocking HKTs has been that though many folks want it there hasn't really been anyone willing to champion it yet. No pressure though - it is a tough problem.
I'm looking forward for even better support of iOS with the support of arm64, I think it is really important to offer an alternative.
BTW is there an RFC on dynamically sized types? I can't find any, I'm looking to learn of it works.
The second production deployment of Rust is a Ruby gem, written in C, that calls out to Rust. It's used in skylight.io, if you're curious.
> BTW is there an RFC on dynamically sized types?
IIRC, DST was before the RFC process even existed, it's just taken forever to implement. The Duke Nukem Forever of Rust. :) http://smallcultfollowing.com/babysteps/blog/2014/01/05/dst-... is what you want to read, IIRC.
Thanks for the info and the link!
Yep! I'm one of the authors of that project. The fact that Rust provides automatic memory cleanup and the attendant safety without runtime overhead (even ARC has non-trivial runtime overhead) was a huge win for us, as was the transparent FFI.
We were looking for a way to write fast code that was embeddable in a Ruby C extension with minimal runtime overhead and without a GC (two GCs in a single process is madness). We also wanted some guarantees that we wouldn't accidentally SEGV the Rails apps we were embedded in. Even last December, Rust was a clear winner for us.
We've been shipping Rust code to thousands of customers for many months, and given the language instability, it's worked really well for us.
* https://github.com/carllerche/hamcrest-rust - a (badly in need of more fleshing out) testing library * https://github.com/carllerche/nix-rust - bindings of Linux/OSX-specific APIs to Rust * https://github.com/carllerche/curl-rust - a binding of libcurl to Rust * https://github.com/carllerche/pidfile-rust - a library for using a pidfile for mutual exclusion across processes * https://github.com/carllerche/mio - a low-level IO library that attempts to implement an epoll-like interface across multiple platforms
I'm looking to learn about how Rust's refcounting memory management works (and how it differs from how, e.g. Objective-C or Swift's runtime-based reference counting works), mostly for personal edification. Can anyone point me to any good resources?
You can find documentation on these types here: http://doc.rust-lang.org/guide-pointers.html
This is different from shared_ptr in C++ which is always atomic and LLVM has to try hard (in clang, no idea what GCC does) to eliminate some redundant ref-counts.
Oh and since Rc is affine, you only ref-count when you call .clone() on it or you let it fall out of scope.
Most of the time you can pass a reference to the contents, or, if you need to sometimes clone it, a reference to the Rc handle, avoiding ref-counting costs.
I've written a regex library, CSV parser, command line arg parser, elastic tabs and quickcheck in Rust. Behold:
$ find ./ -type f -name '*.rs' -print0 | xargs -0 grep Rc
$
I've definitely reached for it a few times as an escape hatch, but I've always ended up finding a cleaner approach to persist without it.Of course, there are plenty of legitimate uses of refcounting. I just haven't hit them yet. :-)
Fixed nonetheless. Do'h.
Sometimes, you need that.
That said, this was discussed on reddit[0], it sounds like there is a way to guarantee that you did zero out memory (but not necessarily copies of that memory, as discussed in the link above), and because Rust is intended to be memory safe, it's not as much of an issue if you don't/can't.
http://www.reddit.com/r/rust/comments/2fnb82/zeroing_buffers...
That means it has the hope of getting it right.
[0] http://doc.rust-lang.org/std/intrinsics/fn.volatile_set_memo...
On minor concern though, I don't see how "where clauses" are simplifying the language. Looks like something that could be added after the release.
Where clasues simplify Rust code, they don't simplify the language itself. They're also important for associated items. For more: https://github.com/aturon/rfcs/blob/associated-items/active/...
What makes you think object inheritance is such a sure thing anyway? I don't expect either object inheritance or optional garbage collection to materialize, ever. In fact, I'd be pretty sad if the language was degraded with that extra complexity - I think it would be a worse situation than the mess that is C++. There would no longer be a shared idiomatic Rust.
That's an interesting move in comparison to Go, which multiplexes coroutines onto threads.
http://chrismorgan.info/blog/introducing-teepee.html#main
There are few REST api libraries as well.
Ambitious might have been a better word than aggresive..
Lastly, Rust's type system actually allows 'this value must be kept local to a thread local' to be expressed, meaning there are two shared_ptr equivalents:
- Arc (Atomic Reference Counting), which uses atomic instructions like shared_ptr
- Rc, which uses non-atomic instructions, and so has much less overhead.
Rust also has move-by-default semantics, so there's no extraneous reference counting due to implicit copying. (Which is particularly bad with the atomic instructions of shared_ptr.)
Furthermore, the type system is designed to be very good for high-performance concurrency.
See http://blog.theincredibleholk.org/blog/2012/12/05/compiling-... for an example of using Rust on a GPU, and I can only imagine that it has become easier since then.