DIP 1017--Add Bottom Type--Final Review
Neia Neutuladh
neia at ikeran.org
Wed Jan 16 07:04:37 UTC 2019
There are at least five parts to this proposal:
a. Add a way to indicate that a function doesn't return.
b. Use a special return type to indicate that.
c. Make that type implicitly convert to absolutely every other type.
d. Make it a compile error to use that type in most ways.
e. Stop running catch/finally after assert errors, throwing exceptions,
etc. I believe this was poor phrasing instead of a real part of the
suggestion.
The proposal should be explicit about having these parts. I feel like the
proposal came this far with as little objection as it did in part because
it was not clearer.
The only points that are at all defended are (a) and (b), and the defense
for (b) was sorely lacking.
> It's necessary to be competitive with other systems programming
> languages.
If we add features merely because other languages do so, we are claiming
that the designers of those languages are authorities to be blindly
trusted. At that point, we can give up on the language and just use Rust
or C++ or whatnot.
That said, it's worthwhile to examine how other languages implement
similar features. We can see what works for them and what doesn't, and we
can see how that might work for D.
The fact that the DIP does not have this analysis is surprising. Let me
make up for this lack slightly.
Rust has a token called never, written !. It is not a type aside from
syntactically. It has two functions: first, it indicates that the function
doesn't return ("diverging", the documentation calls it). Second, it tells
the compiler to infer the type from its later contextual usage. (Or at
least, that's how it worked circa 2014. It might have some other mechanism
for not showing type errors today.)
#![feature(never_type)]
fn temp() -> ! {
panic!("I am panicking");
}
fn main() {
let mut x : ! = temp();
println!("x = {}", x);
x = temp();
println!("hello world");
}
Swift has a type called Never. It's used to indicate that a function
doesn't return -- it panics or throws an exception. It doesn't have any
implicit conversions. You can have variables of its type. You can pass it
to functions. You can assign it to things. It doesn't support the ternary
expression logic that this DIP proposes.
enum HelloError : Error {
case badName
}
func sayHello(name: String) throws -> Never {
throw HelloError.badName
}
struct Foo {
var never : Never
}
do {
var n : Never
n = try sayHello(name: "Todd")
var foo : Foo = Foo(never: n)
} catch {
}
Kotlin has a type called Nothing. It doesn't do implicit conversions. It
supports the ternary logic that this DIP proposes as a side effect (you
can use throw and return in the same way). You can have variables of this
type, pass it to functions, assign it to things, etc.
fun foo(): Nothing {
throw Exception("this doesn't work")
}
fun foo2(n: Nothing) {}
fun bar() {
try {
var n: Nothing = foo()
foo2(n)
var n2 = if (true) { 12 } else { foo() }
} catch (e: Exception) {}
}
C++ uses an attribute and does nothing special with the function's return
type.
Nim uses an attribute, likewise.
Go lets you add a documentation comment, but that's for humans, not the
compiler. There may be internal, undocumented mechanism to tell the
compiler that a particular function doesn't return, intended to be used
only within the runtime; that's the sort of thing the Go team does.
I'd expect the proposal to also include the languages mentioned on the
Wikipedia page for Bottom type that actually have a bottom type. (It has
some egregious inaccuracies, such as claiming that Javascript has a bottom
type named undefined.)
It seems like forbidding variables, function parameters, fields, etc of
the no-return type would be unique for D. It's also mostly unique *within*
D; only void works similarly. Had the author done this sort of review, it
would have been an opportunity to notice that the proposal is unique in
this way and reflect on the advantages and disadvantages.
> RAII cleanup, scope, finally, and catch clauses need not be generated
> for such branches.
This isn't true currently:
void main()
{
scope (exit) writeln("scope exit");
assert(0);
}
This prints "scope exit" as you'd expect. Destructors and catch/finally
clauses are run too.
Is this intended as part of the proposal? If so, that would be a
nontrivial change that would almost certainly be rejected. Or is it
talking about code after the noreturn function? If so, that should be
included in point 1, and it should be rephrased to clarify what "such
branches" means.
More information about the Digitalmars-d
mailing list