Hacker News new | past | comments | ask | show | jobs | submit login

Rust feels a lot like C++ with smart_ptr/unique_ptr by default, just with a different syntax.

Bad things they imported from C++ :

- Traits (a.k.a. I don't known where this method is implemented).

- Over-complicated and compile-time-specialized generics (feel a lot like templates).

- Copy traits feel a lot like C++ copy constructor (a.k.a. I never known what simple things like assignments or passing an object around will actually do) ( https://rustbyexample.github.io/examples/clone/README.html )

- Explicit allocations (stack vs heap) and ownership management.

Yet they added some bug-provoking things like "The final expression in the function will be used as return value". And crazy syntax rules like "ho, only if the expression is not followed by a ;".




> - Traits (a.k.a. I don't known where this method is implemented).

AFAIK, it's implemented either next to the struct definition or next to the trait definition, I don't think it's possible to implement a trait from one library for a struct from an other library.

> - Over-complicated and compile-time-specialized generics (feel a lot like templates).

No, Rust generics are reified (as are C#'s for instance) but they don't do arbitrary codegen, they're just generics, they're not a turing-complete compile-time metalanguage (that's macros)

> - Explicit allocations (stack vs heap) and ownership management.

Rust's very goal is to take on C and C++ with a better language, the inability to manage allocations would be a non-starter. As for ownership, the language formalises and help keep track of something which is only implied in C or C++, which is a good thing (as it moves ownership issues up, front and center, and thus reduces the likelihood of bugs due to muddled ownership).

Note that Rust has Gc and Rc containers, so you don't have to bother if you want (turns out, people like unique/owned pointers and Gc was moved from "language core" to "library" because it wasn't used that much and ended up cluttering the language for little value)

> Yet they added some bug-provoking things like "The final expression in the function will be used as return value". And crazy syntax rules like "ho, only if the expression is not followed by a ;".

Because it's statically typed, there's no possibility of a runtime bug here: `a` has type `A`, `a;` has type `()`. The compiler will tell you to get bent if you use the wrong one.

All in all, your comments read like you want something at a more abstracted level, you should look at, say, OCaml or Go. Which is fine, just not what Rust aims to provide.


I think promoting Rc and Gc in that way is a little disingenuous, since memory safety permeates the whole design, meaning that you can never just throw memory and mutability around without caring like you can in other languages.

They help solve ownership "problems" but can't paper over the interesting part of Rust: lifetimes and memory safety. (i.e. they stop you having to structure everything as a strictly owned tree, allowing DAGs (with plain Rc) and arbitrary graphs (with Gc and Rc + weak pointers).)

However, unique ownership will always be the thing that's easiest for humans, and more importantly, the compiler to reason about, and so shared data will always be slightly harder to work with than non-shared data.


> I think promoting Rc and Gc in that way is a little disingenuous, since memory safety permeates the whole design, meaning that you can never just throw memory and mutability around without caring like you can in other languages.

I'm not sure why it's disingenuous, Rc and Gc are supposed to be memory safe as well, and as you note their point is simply to not bother with ownership.

> However, unique ownership will always be the thing that's easiest for humans, and more importantly, the compiler to reason about, and so shared data will always be slightly harder to work with than non-shared data.

Which, generally speaking, I'd say is an advantage not a drawback.


I just mean saying "Rust has manual memory management but there's Rc and Gc to save you from that" isn't quite true: the hard part of "memory management" in Rust is convincing the compiler that your lifetimes work out (i.e. it's not doing it correctly that's hard, it's getting the compiler to verify what you've done is correct that's hard), and Rc and Gc don't really help with that, except if you use them everywhere (and the stdlib purposely doesn't use them everywhere, so you will hit lifetimes at some point in your codebase).

> Which, generally speaking, I'd say is an advantage not a drawback.

Of course, but being an advantage doesn't change the fact that Rc/Gc are not a solve-all-your-memory-management-issues tool in Rust.


I think you're in violent agreement with masklinn. His point is your point: it is possible to avoid memory lifetime issues by using garbage collection, but that is not idiomatic Rust, and the design of the language encourages you to reason about memory lifetimes.


I'm not disagreeing with them, yes, but I do think people tout Rust's Rc and Gc (the latter og which doesn't even exist properly yet...) as overly optimistic solutions to escape Rust's variation of "manual" memory management: they don't entirely free you from the shackles of the borrow checker, and even introduce worse lifetime issues (failure at runtime as soon as (certain types of) mutability is required).

That said, the lifetime system make it hard to get things dangerously wrong, so I'm quite happy with the tradeoff Rust offers.


If you use RC and RefCell properly, there shouldn't be dynamic borrow failures any more than there are ConcurrentModificationExceptions in Java or NSEnumeration failures in Objective-C. Sadly most people don't use them very effectively; it seems more work is required to make the right patterns more obvious and easy to use.


Of course, if one uses things right then stuff works fine... It's relying on people to do things right that's tricky (which is why we have Rust).

My point was mainly that you lose much of the compiler's assistance by moving from singly- to multiply-owned data.


(note that dbaupp is a core rust developer[0], if he disagrees with my statements I'm probably missing something somewhere)

[0] https://github.com/mozilla/rust/commits?author=huonw


If you actually believe that, you have not really looked. It adds e.g. algebraic data types, pattern matching, actor based concurrency, it's more functional, has pretty good macros, has the compiler do more heavy lifting etc.

> Explicit allocations and ownership management.

It has optional GC. And it's not like automatic memory management comes at no cost.

The last thing makes sense if you think of a semi-colon as an operator turning an expression into a statement. And since it's typed, it won't let you do something dumb.


> If you actually believe that, you have not really looked. It adds e.g. algebraic data types, pattern matching, actor based concurrency, it's more functional, has pretty good macros, has the compiler do more heavy lifting etc.

My point is that they have borrowed really bad features from C++. Pattern matching, Option, etc are cool; but the C++ features are ruining it, IMO.


It doesn't sound like you are a systems programmer. That, and several of the points you mention lack substance.

> - Explicit allocations (stack vs heap) and ownership management.

This is a great feature. If you're writing another web app, then sure, you don't care about stack vs heap. If you're writing performance oriented code, then you do. Both D and C# allow for a similar mechanism.

> Copy traits feel a lot like C++ copy constructor (a.k.a. I never known what simple things like assignments or passing an object around will actually do)

The assignment operator is not overridable.

> Yet they added some bug-provoking things like "The final expression in the function will be used as return value".

Yet you provide no examples on how this is bug provoking. There have been exactly 0 bugs due to this feature.

In short, it seems that you need to read about Rust in more details before making unbased claims.


> It doesn't sound like you are a systems programmer

> it seems that you need to read about Rust

Please keep personal slights out of HN comments. This comment would be much better without the first and last sentences.


Points taken :)


> > - Explicit allocations (stack vs heap) and ownership management.

> This is a great feature. If you're writing another web app, then sure, you don't care about stack vs heap. If you're writing performance oriented code, then you do. Both D and C# allow for a similar mechanism

Some languages do that for you. Allocating on the stack by default and falling back to the heap when it wouldn't be safe to allocate on the stack. e.g. if the object can outlive the current function call, it shouldn't be allocated on the stack.

If the Rust compiled doesn't known how to spot unsafe stack allocations, then you don't have memory safety.

> > Copy traits feel a lot like C++ copy constructor (a.k.a. I never known what simple things like assignments or passing an object around will actually do)

> The assignment operator is not overridable.

That's not what I said. Operator overloading is yet an other feature that's ruining everything else, though.

> > Yet they added some bug-provoking things like "The final expression in the function will be used as return value".

> Yet you provide no examples on how this is bug provoking. There have been exactly 0 bugs due to this feature.

I may be wrong on this one, since type checking would spot most problem at compile time.


> Some languages do that for you. Allocating on the stack by default and falling back to the heap when it wouldn't be safe to allocate on the stack. e.g. if the object can outlive the current function call, it shouldn't be allocated on the stack.

There are several problems with this approach for systems programming.

1. Escape analysis is conservative. Whenever you have an indirect function call or a call in another module, you have to assume that it will cause a value to escape. By making the lifetime part of the type, Rust can keep values on the stack even when indirect or cross-module functions are involved.

2. When you put a value on the heap, how do you know when to destroy it? All industry languages with escape analysis require a garbage collector, which reduces application throughput in the mark phase. But in Rust, with unique ownership, you can make that information known at compile time in most cases.

3. Not all heaps are created equal. There are many kinds of allocators—thread-local allocators, bump allocators, global allocators, etc.

4. Garbage collectors don't work very well with external libraries that don't use the same memory management scheme. By not requiring a garbage collector or a standard heap, Rust can interoperate better with other languages. For example, you can write libraries in Rust that plug into Ruby with no extra runtime support.

> That's not what I said. Operator overloading is yet an other feature that's ruining everything else, though.

Operator overloading is really important for things like bignums and custom containers like vectors. I think the Rust way of using traits for this (meaning that the operators always have consistent types, and have to be defined in one place) makes it much less confusing than the ad-hoc approach of C++.


Thanks for this insightful answer.

About operator overloading, some languages have seemless bignums without allowing users to overload operators in their own classes. For containers, this could have been restricted to the index operator.


But it's not just about indexes or certain containers. Sure, C++ makes things unwieldy, but the solution is not to drop operator overloading in its entirety. Rust wants to take on C++ head to head. For graphics programming and certain applications, operator overloading makes life a lot easier, and is a very welcome addition.

Take a look at Scala for an arguably worse implementation of operator overloading. Though still, the IDE helps a lot there.


Surely "a + b" is nicer to the eye than "a.add(b)", but unless a and b are numbers, a method call with a meaningful name is more straightforward than any abuse of allowed operators.

> Take a look at Scala for an arguably worse implementation of operator overloading.

Indeed

> Though still, the IDE helps a lot there.

If I need an IDE to understand some code, and tend to stay away from that code/language.


What if a and b are numbers? What if they're matrices? What if they're quaternions -- or higher? What if they're finite fields? Additive groups? Points on an elliptic curve? Random variables?

Operators (+ - * / ^ & | ¬) come from mathematics. While I'm sure there are plenty of node jockeys who have little use for numbers outside of specifying ports, there are still plenty of us for whom operator overloading is a necessary good.


> Some languages do that for you. Allocating on the stack by default and falling back to the heap when it wouldn't be safe to allocate on the stack. e.g. if the object can outlive the current function call, it shouldn't be allocated on the stack.

> If the Rust compiled doesn't known how to spot unsafe stack allocations, then you don't have memory safety.

This seems like a weird thing to say given that the Rust compiler does fairly sophisticated analysis on the lifetime of data. (And yes, Rust has memory safety.)

As a low level language, the programmer decides explicitly what is allocated on the stack and what is on the heap.


Is there many cases where it's safe to allocate an object on the stack, but you would want to allocate it on the heap instead ? (Appart from the the object being too big.)


As I said, it's about making costs explicit. Otherwise, you're relying on the compiler's escape analysis to choose whether to allocate something on the heap or the stack. A programmer might not realize that something escapes, and thus, there is a heap allocation that the programmer thought was a stack allocation.

It's worth something to be able to say, "put this on the stack dammit" and if it can't go on the stack safely, require that the compiler yell at you.


We needed those features--explicit memory management, for example--to write a browser engine, and we know others need those features too. (For example, game developers are really not OK with a language that hides the distinction between the stack and the heap.)


It sounds to me like you don't want a low-level programming language. That's totally fine! Rust isn't going to be amazing for everything.


Depending on what I'm doing, I do want a low-level language. Still, Rust's memory management feels weird and painful.

But above that I'm criticising features such as operator overloading, overridable copy semantics, and traits.


Rust explicitly doesn't allow overriding `=`.

Rust doesn't have templates, but rather parametric polymorphism and traits are part of that, they are used exactly like typeclasses in Haskell.

Static typing means a dropped semicolon never introduces a bug (a meaningfully forgotten semicolon almost always results in a compile error, I've never met a problem).


> Rust explicitly doesn't allow overriding `=`.

In part 25 Copy and clone: "copied by default instead of moved, because these types implement the Copy trait"

So implementing the Copy trait allow to modify the semantics of assigning an object to a variable, or of passing the object around; am I right ? Feels a lot like C++'s copy constructor.

> Rust doesn't have templates, but rather parametric polymorphism and traits are part of that

This is just an other name for templates.

> Static typing means a dropped semicolon never introduces a bug

Using the last expression as return value feels weird in a language that makes everything so uber-explicit, like ownership, stack vs heap allocation, etc


Copy is a trait that is automatically implemented by the compiler (and cannot be manually overridden) that basically just says "semantically this type can be copied with memcpy", it doesn't actually control the runtime behaviour of =. Passing around a Copy type by-value leaves the source still usable ("a copy"), any other type leaves the source unusable at compile-time ("a move"); assignment is just a normal by-value use of the right-hand side. (The phrasing in that section isn't as clear as it could be.)

They are templates with checking at the declaration, not the instantiations, so you don't get the errors nested deep inside libraries with ridiculous chains of messages.

Don't knock the semicolon thing until you try it, it's actually pretty nice. :)


> Copy is a trait that is automatically implemented by the compiler (and cannot be manually overridden) that basically just says "semantically this type can be copied with memcpy", it doesn't actually control the runtime behaviour of =. (The phrasing in that section isn't as clear as it could be.)

That's a very good news. Indeed that section is not clear, it looks like user defined classes could implement the copy trait, which would be really bad.

Thanks for your answers.


(BTW, I edited some more details about copy vs. move into my previous comment just after you replied.)


I haven't dealt much with c++ templates but they are fully turing complete. I believe rust generics are weaker and more like java. In c++ templates try to see if a type can be used in a function. In rust you can only use a function if the type fulfils a trait which seems a lot like generic constraints in java/c#.

I don't think making things explicit is a goal of rust. Making things safe as possible by default while being fas is. A good example is that most local variables are type inferenced rather then declared.


Making _costs_ explicit is a goal of Rust. Stack allocation is syntactically cheaper than heap allocation, for example.


Sadly, operator overloading is not a really good way of making costs explicit.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: