Earlier quoted context omitted.
Rust doesnt have objects, but otherwise is very compelling. I still use C++ but I'm also looking forward to the day (soon) where I can use Rust instead for many projects. With more languages coming to LLVM I expect there will be other solutions as well. C++ took years until it got a ranged for loop. It was so important you had frameworks like Qt providing their own preprocessor to support this functionality. C++ is a…
Indeed, but you're refuting Animats' comment, and you and I happen to be in agreement that Rust and C++ are nowhere comparable in terms of language complexity (largely due to C++'s C-compatibility and the burden of 30 years of backwards-compatible language evolution). My original question still stands, because I'm curious to know how he perceives Rust's complexity to be equivalent to that of C++'s.
Ill-Advised C++ Rant, Part 2
51–60 of 66 posts
Re: Ill-Advised C++ Rant, Part 2
#52Earlier quoted context omitted.
Why is it essential that anything is inlined? Maybe if the compiler isn't inlining it had a good reason for that? Maybe it has some understanding of the trade off between specialisation and code size for these particular functions which you don't.
In most cases I would wager I have a better idea of where I want the optimizations to be applied in code than the compiler does. Macros, unlike inline functions, will always give you the result you are looking for. You don't have to worry about things like regressed performance because a new compiler versions has tweaked inlining heuristics. The use of "inline" is diminished in much real world C++ too. Because it's u…
Nobody in this thread has said that.
Re: Ill-Advised C++ Rant, Part 2
#53Earlier quoted context omitted.
As someone trying to learn/experiment with Rust, the syntax is pretty complex. Now granted it comes with some benefits, and I realise there are a limited number of characters available to use (at least that everyone in the world has on their keyboards), but stuff like the lifetime char ' are IMO way too easy to mistake when quickly glancing at code for strings. But maybe that's just me.
No, it's not just you. The more I look at Rust, the less I like. -- leaving off the semicolon on the last statement causes that to be the return value of a function. -- functions can't capture free variables in lexical scope. They need an entirely different type and syntax for that (closures). -- try reading some non-trivial Rust code that uses generics - it's just as incomprehensible and unmaintainable as C++. It's…
Re: Ill-Advised C++ Rant, Part 2
#54Earlier quoted context omitted.
Why is it essential that anything is inlined? Maybe if the compiler isn't inlining it had a good reason for that? Maybe it has some understanding of the trade off between specialisation and code size for these particular functions which you don't.
In most cases I would wager I have a better idea of where I want the optimizations to be applied in code than the compiler does. Macros, unlike inline functions, will always give you the result you are looking for. You don't have to worry about things like regressed performance because a new compiler versions has tweaked inlining heuristics. The use of "inline" is diminished in much real world C++ too. Because it's u…
Re: Ill-Advised C++ Rant, Part 2
#55I mean, finally getting standard networking and filesystem libraries is a really nice thing...
It depends on your definition of "standard." POSIX is a standard, and it's the standard nearly everybody has used for networking and filesystem access.
Re: Ill-Advised C++ Rant, Part 2
#56I mean, finally getting standard networking and filesystem libraries is a really nice thing...
It depends on your definition of "standard." POSIX is a standard, and it's the standard nearly everybody has used for networking and filesystem access.
Re: Ill-Advised C++ Rant, Part 2
#57Earlier quoted context omitted.
Rust encourages writing imperative code in a functional style, like this: fn run_query() -> Result { PostgresConnection::connect("postgres://localhost:5432/postgres", &NoSsl) .and_then(|conn| conn.prepare("SELECT ir FROM x")) .and_then(|stmt| stmt.query([])) .map_err(|e| format!("{}", e)) } This is a strange way to write control structures. Each object gets to define its own control structure syntax. Then there's the…
The try macro is going to be replaced by cleaner syntax soon (along with control flow based catch syntax). It's not "a layer of macros", its one macro, which everyone knows about, so it's not invisible. No different from a return or throw statement -- the control flow escape hatch is "invisible" there, too, but everyone knows what a return/throw are, so it's perfectly visible. It's the same situation with try -- ever…
My dream language would have catchable, unchecked, untyped exceptions. I.e. you can throw and catch strings anywhere without declaring that upfront. You can even throw from a destructor while unwinding, whereupon the new string gets appended to the old string and life goes on. It's a pretty sweet design:
1) Easy to read code with standard control constructs.
2) No performance overhead in the common case.
3) No dispatching on error types, therefore less temptation to use errors for control flow.
4) No distinction between recoverable errors (option) and unrecoverable errors (panic). I feel that distinction is in the eye of the beholder, especially if you need to catch errors from code you don't control.
5) No distinction between code that can cause errors and code that cannot, thus higher-order functions become easier to write.
I'm hard pressed to name any advantages of Rust's model compared to the above. Rust can't even claim to be transparently callable from languages that don't support exceptions, because panics exist :-(
Re: Ill-Advised C++ Rant, Part 2
#58Earlier quoted context omitted.
The try macro is going to be replaced by cleaner syntax soon (along with control flow based catch syntax). It's not "a layer of macros", its one macro, which everyone knows about, so it's not invisible. No different from a return or throw statement -- the control flow escape hatch is "invisible" there, too, but everyone knows what a return/throw are, so it's perfectly visible. It's the same situation with try -- ever…
For what it's worth, I agree with Animats that Rust's error handling is too complicated. My dream language would have catchable, unchecked, untyped exceptions. I.e. you can throw and catch strings anywhere without declaring that upfront. You can even throw from a destructor while unwinding, whereupon the new string gets appended to the old string and life goes on. It's a pretty sweet design: 1) Easy to read code with…
Yeah, it is, but it wouldn't interact well with Rust's safety guarantees :)
(You're analysing this feature in a vacuum, but that's not how language design is done.)
> Easy to read code with standard control constructs.
I think this is in the eye of the beholder, `try!` or `?` or `catch` are (would be -- for the latter two) easy to read for Rust folks. It's just different. do-notation in Haskell is similarly "standard" for functional types, but it's totally alien to C++ folks. The main reason folks find try/catch/throw easier to understand is because they're used to it. Once I learned how enums worked in Rust, `Result` was a very straightforward and simple thing. I actually personally find `try!` easier to read, since I know exactly where a return can happen, unlike C++ where the control flow is totally obscured.
This leads to the rug getting pulled out from underneath you, which can be bad for safety. In fact, Rust's `recover()` needs to be careful about types allowed to cross a recover boundary so that it can be 100% memory safe.
So the code might be easy to read, but not easy to reason about.
> No dispatching on error types, therefore less temptation to use errors for control flow.
I don't get what you mean here. Rust doesn't dispatch on error types, though it does dispatch on Result (which may contain an error). I don't see what's wrong with this. This lets you program in a functional way (and like I mentioned, you don't have to), which isn't a bad thing.
Errors _are_ a control flow thing, you can't escape that. C++ handles the control flow around errors with try, throw, and catch, Rust does it with Result and its methods (and the try macro).
> especially if you need to catch errors from code you don't control.
`recover()` exists. Use it sparingly; it's basically only for cases when you want to catch panics in code you don't control (even then, try other solutions), or stop panics from crossing FFI boundaries.
Panics are supposed to be for irrecoverable or impossible things, where "irrecoverable" is usually a statement that only makes sense in an application, not a library. So this problem in theory shouldn't come up (I haven't seen it happen much in practice).
I think the problem of forgetting to catch an exception from code you don't control because you don't know it's there is a much more pressing problem than having stray panics (since panics are relatively rare).
> No distinction between code that can cause errors and code that cannot, thus higher-order functions become easier to write.
Result is also a type. Treat it as any other return type in a higher order function and it works. In `Fn(...) -> T`, `T` can be a Result type, no problem.
> Rust can't even claim to be transparently callable from languages that don't support exceptions, because panics exist
and so does `recover()`.
> No performance overhead in the common case.
IIRC it doesn't turn out to be much, but ICBW. I think someone looked into this.
Re: Ill-Advised C++ Rant, Part 2
#59Earlier quoted context omitted.
For what it's worth, I agree with Animats that Rust's error handling is too complicated. My dream language would have catchable, unchecked, untyped exceptions. I.e. you can throw and catch strings anywhere without declaring that upfront. You can even throw from a destructor while unwinding, whereupon the new string gets appended to the old string and life goes on. It's a pretty sweet design: 1) Easy to read code with…
> It's a pretty sweet design Yeah, it is, but it wouldn't interact well with Rust's safety guarantees :) (You're analysing this feature in a vacuum, but that's not how language design is done.) > Easy to read code with standard control constructs. I think this is in the eye of the beholder, `try!` or `?` or `catch` are (would be -- for the latter two) easy to read for Rust folks. It's just different . do-notation in…
About memory safety: can you give an example where a naive implementation of catchable exceptions would break memory safety? I know about RecoverSafe but it seems to be solving a different problem (marking types whose logical invariants are preserved in case of panic).
Re: Ill-Advised C++ Rant, Part 2
#60Earlier quoted context omitted.
> It's a pretty sweet design Yeah, it is, but it wouldn't interact well with Rust's safety guarantees :) (You're analysing this feature in a vacuum, but that's not how language design is done.) > Easy to read code with standard control constructs. I think this is in the eye of the beholder, `try!` or `?` or `catch` are (would be -- for the latter two) easy to read for Rust folks. It's just different . do-notation in…
About higher order functions: putting effects in the type forces you to make a distinction between map and mapM. You can't unify them because one takes a -> b and the other takes a -> m b. Same for any other higher order function, you have to write two versions (or more if you don't have HKT). About memory safety: can you give an example where a naive implementation of catchable exceptions would break memory safety?…
You have the same distinction in exceptions? One map would bubble the exception, the other would internally catch and exclude. You don't have to write two versions, map still works for cases with and without Result, but it will not have the additional behavior of excluding errors. You want that additional behavior, hence you write mapM; just like you would in C++.
> About memory safety: can you give an example where a "naive" implementation of catchable exceptions would compromise memory safety? I know about RecoverSafe but it seems to be solving a different problem (marking types whose logical invariants are preserved in case of panic).
There's an example in the rfc: https://github.com/rust-lang/rfcs/blob/master/text/1236-stab...
In Rust logical invariants are used to enforce memory safety. If the destructor of something within a vector panics, that might result in an invalid vector. There's a pattern colloquially called "pre pooping your pants" which would help in this specific case (http://cglab.ca/~abeinges/blah/everyone-poops/), but not in general. Writing unsafe code in Rust involves careful bookkeeping if you want to be sure the code is safe; and that goes out the window when the rug can be pulled out from underneath you any time.