Hacker News new | ask | show | jobs
by dave_ops 3885 days ago
This has been my problems with Rust for the last 10-12 months.

Lifetime elision seems thoroughly broken or extremely limited in functionality, and now when I'm writing (sometimes seemingly trivial) solutions in Rust I'm spending at least as much time and mental energy explicitly annotating lifetimes as I would be if I was just managing malloc and free in C.

As a result the pain vs. benefit curve doesn't bend nearly as far toward Rust as it theoretically should.

4 comments

I'm interested in seeing your use cases where lifetime elision isn't helping. I've found that it's helpful in the vast majority of cases, and when it isn't working (for functions, at least) there's a decision to be made.

It also could be a programming style thing; I've seen newcomers from C++ often trying to program in the C++ style and having lifetime troubles because the C++ style isn't really amenable to Rust's model. (I feel this might be the issue since you mentioned "sometimes seemingly trivial" -- almost every time someone has said something like that about Rust it's a matter of a programming pattern not translating directly)

Also, really, lifetimes don't do away with the effort required in manual memory management, they just confer it to compile time, so at least you can be sure that your code works and will not break in the future. They're not really a new concept, you think about them anyway in C++, just in a different way.

I'm a functional programmer (Lisp, OCaml, Erlang), and I only touch C++ when I have to wrap it in something to interface with a higher-level language.

Most of the cases where I run into this problem and end up feeling like I didn't gain much, if anything, from doing a straight C implementation are situations with deeply nested data structures.

I get that the thinking is that the benefit is that once I've made the compiler stop complaining, my memory management model should at least be sound and safe, and that is a win. Though immediately following that I start to have dream-like fantasies where this entire static analysis stage is simply bolted onto C instead of being a whole new language.

Afterall you can do some pretty impressive stuff with nothing but a pile of preprocessor macros (see Objective C).

> Most of the cases where I run into this problem and end up feeling like I didn't gain much, if anything, from doing a straight C implementation are situations with deeply nested data structures.

Your feeling doesn't line up with what has been observed in practice. Empirically, people do not get the memory management right in C. They mess up, again and again and again. In Rust, however, the compiler enforces that you get it right.

> Though immediately following that I start to have dream-like fantasies where this entire static analysis stage is simply bolted onto C instead of being a whole new language.

I don't think it's really possible to bolt Rust's semantics onto C. Too much will break: in particular C code is not written with inherited mutability, which is critical to Rust. You could maybe do something like the proposed lifetime stuff in C++, which still requires lots of annotation, enough to effectively be a whole new language.

Rust has a lot better ergonomics anyway.

> Afterall you can do some pretty impressive stuff with nothing but a pile of preprocessor macros (see Objective C).

Objective-C isn't "a pile of preprocessor macros", and it would be completely impossible to implement the lifetime system using the preprocessor.

The first versions of Objective-C were most definitely implemented as pre-processors to C compilation.
That's not the same as using the C preprocessor to implement the language.
At this year's CppCon Herb Sutter the chair of the ISO C++ committee addressed just this issue of lifetime safety. Here is a video of his talk and includes a demo of the upcoming static analysis tool that can do this with very minimal annotations https://m.youtube.com/watch?v=hEx5DNLWGgA

I remember thinking when watching the talk that this was a very bad day for Rust as C++ could now do most of what Rust was promising

The lifetime stuff in C++ is full of annotations, with effectively just as many required as Rust. I think of it effectively as a separate language. It's also significantly more complex, with points-to analysis instead of inherited mutability, and the restrictions on pointer aliasing on function entry seem very restrictive compared to what RefCell provides.

I think that Rust's system has proven itself to be about as simple and easy to use as you can get in this space. I also think that you cannot just "bolt on" a lifetime system to an existing language that was not designed for it without large amounts of annotation, modification of existing code, and additional learning curve.

The danger here to Rust is the usual "worse is better".

OS vendors will happily just add GSL and static analysis driven support to C++, with Rust becoming the language that drove them to do that and that is it.

Microsoft (GSL + static analysis, C++/CX, System C#, .NET Native) and Apple (Swift) are already working on what might be their next systems programming language.

They might lack full Rust like safety, but they are a good enough incremental approach to safety, come with integration with their existing tools and libraries.

Which leaves Rust adoption for systems programming to other OS vendors, e.g. embedded space or open source OSes.

Remember, languages are a tiny portion of the whole eco-system. Tools, libraries and community play a bigger role.

I agree. We are not starting out with a blank slate. There are already billions of lines of C++ in mission-critical production use. Companies such a Microsoft, Apple, Google, and Facebook run on C++. In addition, it has vendor support on all the major operating systems. In contrast, not even Mozilla, has bet the company on Rust (Servo is experimental and Mozilla has made no promises of actually integrating it into Firefox.) Also, Rust is not vendor supported on any platform.

So if you are a CTO or someone who has the responsibility of deciding what language to use for your performance critical software, you already have a large bias toward C++. You may be swayed toward Rust by the fact that Rust is memory safe. However, when someone comes and tells you that with this open source tool for C++ backed by the big names in C++, and developed and supported by Microsoft, and just by using the GSL types you can get most of the benefit of Rust, you will decide that the delta of improvement of Rust over C++ is not worth it.

TLDR: In programming language use pragmatic considerations trump purity. Multibillion dollar companies run on C++. With GSL and static analysis, C++ is "good enough".

Exactly! No matter the future of Rust, it has already shaken the programming community and motivated the other language designers to catch up in that domain.

As to guess whether Rust will put down C++ forever and ever, is another hard question. It will be a matter of community and sadly money... For instance, Apple's already got a much better market share with Swift (look at the job trends) simply because it's Apple. Same goes for JavaScript greatly helped by Google (V8) for their interest (a faster JavaScript engine ==> better usage of Google's web services).

> Microsoft (GSL + static analysis, C++/CX, System C#, .NET Native) and Apple (Swift) are already working on what might be their next systems programming language.

Swift at least isn't in the same space as Rust. It's a fully garbage-collected language.

> Remember, languages are a tiny portion of the whole eco-system. Tools, libraries and community play a bigger role.

And Rust has the most memory-safe low-level systems code available today. It also has Cargo and crates.io, which allow that code to be easily reused.

By contrast, existing C++ code isn't safe C++ code. It will have to be ported, often by drastically altering the idioms in use, and that takes a lot of time.

While I'm glad to see C++ improve, and I'm happy that Rust seemed to influence this, I don't think that it will kill either Rust or C++.

As you say, there's a ton of C++ code out there. However, five things:

  1. A _lot_ of that is C++98.
  2. These rules are just proposed. There's tons of time.
  3. They don't give you as much safety as Rust.
  4. Many people have already rejected C++.
  5. Langauges don't die.

So, one and two are kind of related. We're still in the "propose" stage for all of this, which means there are still years before this would make its way into the standard. How long have things like modules taken? Who knows when the rules will be final, and when they'll actually land. You get all of this (and more) in Rust right now.

As for three, while these rules help, and are laudable, they still don't give you data race freedom. In general, they're not intended to be an ironclad safety system, just something to help catch more cases where something went wrong. See Herb's insistence that pointers should be able to dangle in some circumstances, as an example. I saw a comment on Reddit that straddles three and four, here it is: https://www.reddit.com/r/programming/comments/3m6j2c/cppcon_...

   > Just follow this set of hundreds of rules (seriously, go read the core
   > guidelines), ignore 20 years of material written about C++ before the
   > year 2011, use this non-standard third-party library of stuff we
   > really like, and you too can have a fraction of Rust's compile safety.
This is phrased a little to sarcastically imho, but there's a nugget of truth there. One of C++'s problems is that it's just got too many features, bolted on over decades. Is bolting on yet another feature the way forward? How much of that existing C++ code will even be able to take advantage of these new safety features?

A lot of people have already rejected C++, for various reasons. The details of those reasons are varied, and may be good reasons or bad reasons, but the point is, they've already said nope. A lot of people coming to Rust fit in this category. "I tried C++ one time, but it's too complex." "I can write some basic C++, but I'm not confident my code is correct." "I write Ruby code, C++ looks terrifying, and I hear it's hard never tried it." And so on. Our industry is huge, there are a lot of developers, and not all of them will use a single language even if it's 'better.' That cuts both ways.

And ties into five. I don't think the question "Will Rust kill C++" is well formed. A language that's used as much as C++ is will never die. We'll have C++ code in production for at least my lifetime, I'd bet. Rust doesn't need for C++ to go away. Languages aren't the Highlander, there can (and often are) more than one. I mean, look at Python and Ruby. They're really similar languages from a PLT perspective, but they're both active, healthy, used-by-millions languages, and many of their programmers wouldn't switch to the other. This is totally fine.

Also Rust has the freedom to just leave dangerous features out of the language [1]. This is something we will never be able to do with C++ without transforming it in a not backwards compatible manner.

And it's not only about safety: a language that doesn't suffer from the consequences of legacy design choices is just less mental burden, easier to learn and easier to teach.

[1] http://graydon2.dreamwidth.org/218040.html

One can simply ditch the C++ backward compatible features into an "unsafe" part of C++. That's exactly what the talk given by Herb at the CppConf is trying to address. All the old unsafe part of C++ will trigger errors or warnings. But if truly necessary one will be able to disable these protections!
I was at the presentations, and basically other than owner<T>, and [[suppress]] I do not remember other annotations. In fact, Herb made a point that having too many anotations was a weakness of Rust, and that they wanted to not have those annotations. There were some types to use such as array_view, string_view etc, but not annotations. Perhaps you are thinking of Herb's slides where he shows things like _In_reads_(num). Those are not part of GSL. He was just contrasting the annotation heavy way Microsoft does static analysis now, with the GSL which does not have these annotations.
I'm referring mainly to [[lifetime(this)]] and friends. Those correspond directly to the lifetime annotations in Rust. Read the paper for more details.

> In fact, Herb made a point that having too many anotations was a weakness of Rust, and that they wanted to not have those annotations.

That's because Herb didn't understand Rust.

> Perhaps you are thinking of Herb's slides where he shows things like _In_reads_(num). Those are not part of GSL.

No, I'm referring to the annotations I saw in the paper: https://github.com/isocpp/CppCoreGuidelines/blob/master/docs...

  >  Herb made a point that having too many anotations was a weakness of Rust,
While he did say that, some other comments of his made it sound like he had checked out older Rust, without elision and with pointer sigils and stuff.
Check out the actual paper on the isocpp github repo. There are `[[lifetime(foo)]]` annotations.

Their proposal does include more elision than Rust (which actually leads to a loss of expressivity of patterns that arise in complex systems), but they still have plenty of annotations.

Functional idioms don't translate cleanly either :)

If you're using Rust for FFI from functional languages (or any language, really) you need to use a fair amount of unsafe code to get the interface correctly. There's not much benefit for simple things (aside from cases like these: https://gist.github.com/steveklabnik/1a3ec0ca676aaddf766e), but for more complex things it works out pretty nicely.

But there's no compile-time validation of malloc() and free()---there aren't even strict run-time checks, right, it's possible for freeing an invalid pointer to quietly corrupt memory instead of segfaulting, right?

So if these annotations took the same amount of effort as manually managing malloc() and free(), they'd still be strictly better because they're validated at compile time, no?

That can be a problem. Some common C/C++ idioms do not translate to Rust. In particular, a function which creates and returns an object is difficult to express in Rust. In Rust, you have to create the object before the call, then pass it to a function to be filled in. Either that, or go with a reference-counted type.
Rust functions return expressions, not objects and it is entirely up to the compiled code at the function call site whether it is placed in the stack or the heap, and whether the function code is inlined or not (thus expanding the stack allocated in the parent function). ::new doesn't create a structure, it just returns the expression that is supposed to initialize the struct in memory, which the compiler writes to the stack or to the heap through the Box constructor.

If you look at the antipattern example in the "Returning Pointers" section of the Rust book [1], you'll see how you are supposed to handle object creation (the box syntax is the same as Box::new) in a Rustic way

[1] https://doc.rust-lang.org/book/box-syntax-and-patterns.html

What makes you think you can't create and return an object? This is how every constructor function in rust works.
I believe this is referring to '...and keep a mutable reference to it' ie. A singleton, and many overseer style observe-and-update-on-change data binding patterns.

Basically, shared ownership is pretty central to many C++ patterns, which means they translate poorly into rust.

Are you referring to objects that contain inner pointers? Otherwise you can just return T or Box<T>.
Has "Box" settled down yet? The last time I looked, box syntax and semantics were still in flux.
Box is stable and fully supported. The built-in "box" syntax is unstable still, but Box::new suffices in almost all cases.
Yeah, at some level of complexity you do start needing some Cell/RefCell/Rc, but it's not much.
> Lifetime elision seems thoroughly broken or extremely limited in functionality

Do you have examples? Have you filed bugs?