News for hackers. Stuff that matters.
Re:Implications of Rewriting a Browser Component in Rust by masklinn on Thursday February 28, 2019 @12:18PM (#19272975)
> For one thing, idiomatic C++ bounds checks by default. You need to use at().

Sounds like it doesn't check by default then. It checks if you remember to check using the more verbose bounds-checking method. Not unlike the issues with subscripting std::map.

Re:Implications of Rewriting a Browser Component in Rust by coldtea on Thursday February 28, 2019 @12:49PM (#19273306)
>Sounds like it doesn't check by default then.

Yeah, in the phrase "idiomatic C++ bounds checks by default", the term "idiomatic" is a "weasel word" that disqualifies "by default".

Re:Implications of Rewriting a Browser Component in Rust by TheAsprngHacker on Thursday February 28, 2019 @12:31PM (#19273108)
I have some experience in C++, and I am familiar enough with the standard library to remember that operator[] doesn't check bounds while the at member function does. I would assume that the Firefox C++ programmers know this as well. However, maybe I'm wrong or have too much faith?

Or, maybe, the programmer was aware that operator[] didn't perform bounds checking, but opted to use it for some reason? A good way to dissuade people from making unidiomatic choices is to make them more verbose. IMO calling the at function isn't particularly verbose, but if the member function that didn't check bounds were called something like "at_unchecked," perhaps people would be less inclined to use it.

Also, from the snippet in the blog post, note that you can't tell whether the Firefox code used std::vector, C-style arrays, or some non-STL container type. Projects may use their own container types, but your criticism only applies if the programmers were using the C++ standard library.

Re:Implications of Rewriting a Browser Component in Rust by masklinn on Thursday February 28, 2019 @12:58PM (#19273386)
> I have some experience in C++, and I am familiar enough with the standard library to remember that operator[] doesn't check bounds while the at member function does.

Everybody knows you're supposed to check pointers for being null, and yet time and time again developers fail.

As long as you rely on human nature and provide one API which is simple, convenient, obvious and dangerous and one which is complex, inconvenient, non-obvious and safe, you will just drive users towards the former.

> I would assume that the Firefox C++ programmers know this as well. However, maybe I'm wrong or have too much faith?

Just because they know when quizzed doesn't mean they'll always remember when actually doing. Even less so when subscripting is safe in pretty much every other language which provides array subscripting, and ::at… only exists in C++?

> IMO calling the at function isn't particularly verbose

No, but it's still more verbose and less intuitive than [], especially given the above (that tons of languages use [], and very few have an at method)

> A good way to dissuade people from making unidiomatic choices is to make them more verbose.

Indeed.

Re:Implications of Rewriting a Browser Component in Rust by svnpenn on Thursday February 28, 2019 @01:37PM (#19273797)
this is so true

and has been my experience with unwrap

they dont want people to use it but the alternative is so verbose and clunky

Re:Implications of Rewriting a Browser Component in Rust by FreeFull on Thursday February 28, 2019 @01:52PM (#19273972)
The alternative tends to be to propagate the error upwards using the `?` operator, up to some point where it makes sense to handle errors
Re:Implications of Rewriting a Browser Component in Rust by barrkel on Thursday February 28, 2019 @01:56PM (#19274008)
Aka exceptions.

Yes, chaps, that Result<T,E> type is all but isomorphic with checked exceptions, Java-style.

Re:Implications of Rewriting a Browser Component in Rust by masklinn on Thursday February 28, 2019 @02:14PM (#19274161)
The "but" is where all the difference lies though, Result (or Either or whatever you want to call it) is the reification of the sum of a return value and an error, and as such manipulable without having to add dedicated tooling… (which java didn't have either, and still does not).

Amongst other issues it's possible to pipe one through a generic wrapper without that wrapper having to care about it.

e.g. let's say you have an input collection, you map() over it, and the map callback can fail.

In Rust or Haskell you… just do that. And the caller deals with a collection of results however it wants.

In Swift, you need map to be specifically annotated in `rethrow` so it can be transparent to failure (aka can't fail if its callback can't, but can if its callback can).

In Java, you're shit out of luck and jolly well fucked, your generic map can't be generic over generic exceptions, so either it callback can't fail or you need to wrap said callback to convert the checked exception into an unchecked one, and possibly back again outside the map.

So… yeah, they're "all but isomorphic" because they're both implementations of the concept of statically checked fallibility. It's just that java's checked exceptions[0] are a bad implementation of the concept.

Put an other way, a 2018 fiesta or yaris are "all but isomorphic with" a 1960 corvair or a pinto, but you couldn't pay me to take a road trip in a corvair or a pinto.

[0] java's because someone might come up with better ones, though the well's been pretty tainted at this point

Re:Implications of Rewriting a Browser Component in Rust by barrkel on Thursday February 28, 2019 @05:26PM (#19275911)
Lack of parameterisation of exception signature is not the reason why checked exceptions are a bad idea, though. Failure is a function of implementation details; runtime errors are fundamentally an abstraction violation. There's no escape.
Re:Implications of Rewriting a Browser Component in Rust by masklinn on Friday March 01, 2019 @01:56AM (#19278502)
> Lack of parameterisation of exception signature is not the reason why checked exceptions are a bad idea

It absolutely is one of the reasons why they are a bad idea, and why reified results are so much more useable and useful.

> Failure is a function of implementation details

In the original intention, that's what unchecked exceptions were for, with checked exceptions for the reporting of errors rather than failure. That's why java didn't have only checked exceptions.

Re:Implications of Rewriting a Browser Component in Rust by barrkel on Friday March 01, 2019 @05:10AM (#19279239)
Unchecked exceptions were for programmer errors, errors that could be avoided with a sufficiently careful programmer: null pointer check, a divisor not zero check, a list bounds check, etc.

Checked exceptions were for unavoidable errors. Errors that are fundamental to the operation being attempted. They usually occur because the operation interacts with the world outside the program, which means the program is subject to violations of expectations. Network errors. File not found when you try to open it - you cannot test for file existence first without a race.

The reality is that the latter kind of errors are better off as overloaded call signatures: one call variant when you care about the error and want to catch it, another variant when you don't care about specifics of the error and want the whole stack to unwind when the expectation is violated.

Neither of these approaches require checked exception signatures throughout the stack (or Result types for that matter - you can assume I also mean those, due to the isomorphism).

There's a reason .net modeled number parsing with Parse() and TryParse() instead of throwing NumberFormatException like parseInt(). It's because sometimes you care - input came from user and you need to handle it - and sometimes you don't - input came from configuration file and the stack needs to be torn down if you can't parse it.

Picture a stack that looks like this:

    0: <operation that may throw exception of the checked variety>
    1: <code that may be interested in handling error>
    2: <code that doesn't know about implementation details>
       .... could be 10, 100, 200+ methods in this stack dump
    N-1: <code that doesn't know about implementation details>
    N: <request handler or event loop that catches all exceptions>
The great problem with checked exceptions is the methods for stack frames 2 to N-1. Either exceptions are handled at stack entry 1, or at stack entry N. The only job of all the code between is to pass exceptions unmolested back to N.

Those calls may be dynamically bound (whether via vtables or function pointers, objects or closures) and / or dynamically linked (so unavailable to a type system at compile time). In large production programs, control flow will be dynamically determined. It's a fact of life; if it's not for testing purposes, it'll be for deployment flexibility.

I think there's value in the IO monad; in marking functions as the kind of functions that may interact with the outside world. And checked exceptions can work this way. But not unless the error type has a polymorphic storage location, and unwinding the stack is syntactically weightless. I don't ever want to have to change the signature on a dozens of methods just because there's a new implementation detail deep inside a dynamically linked abstraction.

And exception / error wrapping isn't the answer either - it's almost always a bad idea.

Re:Implications of Rewriting a Browser Component in Rust by neel_k on Thursday February 28, 2019 @03:33PM (#19274901)
Semantically, returning a sum type like Result<T,E> absolutely is the same thing as exceptions (ie, it's the exception monad).

However, there's one very important language design difference between this and Java-styled checked exceptions: using sum type gives you effect polymorphism for free. This means you can write (say) a map function which says it has precisely the same exception type of its argument function, using the same machinery you're using for generics everywhere else.

This is a big from the usability side, since AFAICT it was the lack of effect polymorphism that turned people off of Java-style checked exceptions. From a language implementation standpoint, it's also nice not to have to implement type inference twice, once for return values and once for exceptions. :)

Re:Implications of Rewriting a Browser Component in Rust by barrkel on Thursday February 28, 2019 @05:39PM (#19276047)
No, lack of static parameterization of the representation of failure is not the problem with checked exceptions. In fact the prison of being forced to construct a tunnel in the (static) type system to carry your error information, irrespective of how dynamic it becomes, is the torture that kills the idea. Errors are fundamentally the leaks in abstractions. They betray the implementation.

The network error that makes a mockery of pretending something is local. The dynamic link error that discloses a pluggable implementation. And all the steps between giving lie to the idea that errors should be caught and handled anywhere near their occurrence - when the only code that knows about the actual final composition is at the top of the stack, far far away, a perilous journey for a fragment of error info - unless such transport is automated, and not gated at customs, as so many checked exceptions boundaries end up being.

I'm in favour of error values for functions that return errors in normal circumstances. But they should be exceedingly easy to dispatch into an unwind mechanism, if it turns out you're not interested. Almost all of the time, the code in the middle doesn't care. Why should it pay the tariff for the transport?

Re:Implications of Rewriting a Browser Component in Rust by masklinn on Friday March 01, 2019 @02:01AM (#19278516)
> The network error that makes a mockery of pretending something is local. […] I'm in favour of error values for functions that return errors in normal circumstances.

Checked exceptions were intended for the latter, and unchecked exceptions for the latter.

Re:Implications of Rewriting a Browser Component in Rust by bzbarsky on Thursday February 28, 2019 @03:31PM (#19274872)
The relevant code in fact uses a non-STL array container. Unfortunately, the performance of the STL containers is fairly unreliable across C++ standard library implementations and can be very poor in some cases. That makes them harder than it should be to use in code where you need to understand the performance characteristics of your data structures.
Re:Implications of Rewriting a Browser Component in Rust by pjmlp on Thursday February 28, 2019 @01:21PM (#19273619)
Most debug builds across major C++ compilers do support it though.
Re:Implications of Rewriting a Browser Component in Rust by pjmlp on Thursday February 28, 2019 @01:20PM (#19273603)
It is implementation dependent.

operator[]() does not require bounds checking by ISO C++, however most compilers do actually enable bounds checking in debug builds.

Visual C++ certainly does it for example.

Re:Implications of Rewriting a Browser Component in Rust by TheAsprngHacker on Thursday February 28, 2019 @03:42PM (#19274973)
Huh. According to https://en.cppreference.com/w/cpp/container/vector/operator_..., “no bounds checking is performed.” I find cppreference.com generally trustworthy, but maybe it’s wrong here? Or, maybe “no bounds checking” actually means “no guaranteed bounds checking?”
Re:Implications of Rewriting a Browser Component in Rust by pjmlp on Thursday February 28, 2019 @05:21PM (#19275859)
It means “no guaranteed bounds checking”, the standard only requires at() to throw if out of bounds (§ 26.2.4.1, note 15), but leaves unspecified how operator[]() should behave in invalid accesses, only that it isn't allowed to throw.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n471...

Here is the Visual C++ documentation for bounds checking in debug builds.

https://docs.microsoft.com/en-us/cpp/standard-library/checke...

Re:Implications of Rewriting a Browser Component in Rust by int_19h on Thursday February 28, 2019 @08:32PM (#19277229)
You need to look at the text of the Standard itself when it comes to this level of language lawyering. The short answer is that operator[] has undefined behavior if the index is out of range. Performing a check and terminating the program with some kind of runtime error is a legal subset of "undefined behavior", and so it's commonly done in debug builds, but you cannot rely on it in any sense.
Re:Implications of Rewriting a Browser Component in Rust by jgon on Thursday February 28, 2019 @12:31PM (#19273106)
This is like saying the Rust doesn't have memory safety because you can use "unsafe". C++ offers you a safe Api and an unsafe API if you want to go that way. If you decide to use the unsafe api the consequences are on you, the same way that it is on you if you decide to use "unsafe" in Rust.
Re:Implications of Rewriting a Browser Component in Rust by steveklabnik on Thursday February 28, 2019 @12:34PM (#19273143)
The difference is in the defaults. In Rust, the easiest and more common thing ([]) is checked, and the more verbose and uncommon thing (get_unchecked) is unchecked. In C++, it's the reverse.
Re:Implications of Rewriting a Browser Component in Rust by masklinn on Thursday February 28, 2019 @12:50PM (#19273317)
> This is like saying the Rust doesn't have memory safety because you can use "unsafe".

It's the exact opposite which is the point. Rust requires extra work and using the non-default and less convenient way for unsafety, C++ requires extra work and using the non-default method and less convenient way for safety.