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

Rust also has (almost) everything being an expression - things like this aren't uncommon:

    let x = if something {
        foo()
    } else {
        bar()
    }
Things which don't have a logical value evaluate to `()` (the empty tuple), I believe.



> Things which don't have a logical value evaluate to `()` (the empty tuple), I believe.

If Rust follows Scala then `()` is not the empty tuple, but rather Unit (void in C*).


The empty tuple/unit is not the same as void. It has exactly one possible value, void has zero possible values. A way to write it in Rust is `enum Void {}` (an enumeration with no options).


The point is that both void and Unit can only produce a side effect. In everything-is-an-expression based languages Unit is exactly equivalent to void in C*.



It's the same thing. It's a type with only a single value.


`void` in C does not have a value. You can't make a variable and put a void in it because there is no such object as "void".


That is an artificial restriction if C. Also see how ! In Rust is also loosing it's artificial restrictions.


Indeed, and there has been some talk of removing this restriction in C++ because it makes certain kinds of metaprogramming a lot more cumbersome than they should be.


Yes, and () isn't void; the poster above me is wrong. Void is...the absence of a type? It's awkward.

However, () and Unit are.


It's not the same thing, a value of type Unit can only produce a side effect (or do nothing at all).


How is that different from a value of type "empty tuple"?


True, neither are useful as values, though Unit typically conveys programmer intent (to produce a side effect), whereas the empty tuple is, in Scala at any rate, quite rare.

The empty tuple:

    scala> val empty = Tuple1(())
    empty: (Unit,) = ((),)
vs. Unit:

    scala> val empty = ()
    empty: Unit = ()


Isn't that a tuple containing a unit, and therefore not empty?


It's the closest you can get in Scala to represent an empty value whose type is `Tuple`.


The difference between an empty tuple (also known as unit) and void becomes obvious when you deal with vaguely complex trait impls. For example, if you have a trait:

    trait Foo {
        type ErrorType;
        fn bar() -> Result<u8, Foo::ErrorType>;
    }
How would you specify that your type implements Foo in such a way that bar() cannot return an error? If you were to implement it using the empty tuple (unit), like this, it could actually return an error:

    struct Abc{}
    impl Foo for Abc {
        type ErrorType = ();
        fn bar() -> Result<u8, ()> {
            Err(()) // oops, we don't want to be able to do that!
        }
    }
Instead, you can use Void here:

    enum Void{}
    struct Xyz{}
    impl Foo for Xyz {
        type ErrorType = Void;
        fn bar() -> Result<u8, Void> {
            // No way to create a Void, so the only thing we can return is an Ok
            Ok(1)
        }
    }
Aside from the obvious power to express intent (can we return an error without any information attached, or can we not error at all?), this would allow an optimizing compiler to assume that the result of Xyz::bar() is always a u8, allowing it to strip off the overhead of the Result:

    fn baz<F: Foo>(f: F) {
        match f.bar() {
            Ok(v) => println!("{}", v),
            Err(e) => panic!("{}", e)
        };
    }
    ...
    baz(Xyz); // The compiler can notice that f.bar() can never return a Result::Err, so strip off the match and assume it's a Result::Ok
A super-smart compiler would even make sure it's not storing the data for "is this an Ok or Err" in the Result<u8, Void> at all.

Finally, similarly, you can specify that certain functions are uncallable by having them take Void as a parameter.


I don't think anyone is claiming Void in Scala/Haskell/Rust is equivalent to Unit in Scala/Haskell/Rust.

The question here was Unit vs empty tuple.

Up-thread was the question of whether C "void" is more like S/H/R Unit or S/H/R Void.


Upthread was:

> If Rust follows Scala then `()` is not the empty tuple, but rather Unit (void in C*).

The implication is that () == Unit == void. The empty tuple and unit are essentially equivalent aside from name, void is something else.


Regardless of who is right, you are arguing the wrong bit of it. I contend that, to a person, everyone saying "C void is Unit" doesn't think C void is Void. Arguing that Void is not Unit is just completely spurious. Of course Void is not Unit. No one disagrees.

In truth, C void is not exactly either Void or Unit. Like Void, you can't exactly make one... but you can call functions declared to take it and write functions that return it, and really it just means "I have no information to pass" - which is more like Unit.


Type names are irrelevant here. Unit could be called "Tuple0", or be defined as a synonym of a type named such. The semantics are identical.


> For historical reasons and convenience, the tuple type with no elements (()) is often called ‘unit’ or ‘the unit type’.

https://doc.rust-lang.org/reference.html


void in C can't be created, used, or passed around - () can.


But void pointers can, and are often used.


So in Rust, the equivalent of void* with references instead looks like:

    enum Void{}
    fn foo(v: &Void){ ... }
    let bar : Void;
    fn abc() -> &Void{ ...}
    fn xyz() -> Void{ ... }
You can't create a Void, and you can't cast to it either - it's not a bottom type. So you can't put anything in bar. And as a result, you can't create a reference to a Void, so you can't call foo. And abc and xyz just can't be implemented in the first place.

On the other hand, you can do all of these just fine:

    fn foo(v: ()){ ... }
    fn bar(v: &()){ ... }
    ...
    let v = ();
    bar(&v);
    foo(v);
The fact that you can create and use an empty tuple as a value shows that it is not equivalent to Void.

(All statements here are made within the safe subset of the language - unsafe allows access to intrinsics that would allow a Void to be made, and a reference to Void.)


void * isn't related to void (or this discussion), they just reused the keyword.


This example of "everything as an expression" doesn't take it as far as Tcl, though. In the above code snippet, the conditional body is surrounded by braces, which are syntax. In Tcl, the second argument to the 'if' command is also an expression, which only uses braces as a quoting mechanism, if it needs to.


I'm not sure I understand why it matters if the syntax requires braces or not. The things inside the braces are still expressions.


It matters because if the braces are not syntax, you can decide what code to execute as the condition body at runtime.

    set condition-body {puts "Hello, world"}
    if { $condition } $condition-body
To get this to run, you need to splice in the condition-body like so,

    if { $condition } {*}$condition-body
But you get the point.


But the "then" and "else" parts of the Rust "if" are expressions. What you're talking about doesn't seem to be related to which things are expressions, it's more like being able to eval code at runtime.


The if expression is not a persuasive example. C/Java/Algol/etc all have it as well.

x = something ? foo() : bar();


The difference is that C/Java/Algol have different syntaxes for things-as-expressions and things-as-statements, and you can't put blocks in the things-as-expressions. In Rust, blocks are also expressions and so have a result (the result of the last expression in the block), so your expressions inside the if can be as complex as you like.

Since functions also have a block, and the return value of the function is the result of the block, this is much more consistent.


In GNU C, you can use a brace-enclosed statement block as an expression.

Funny story; years before I became a C programmer, and at a time when I didn't yet study ISO C properly, I discovered and used this extension naturally.

I wanted to evaluate some statements where only an expression could be used so I thought, gee, come on, can't you just put parens around it to turn it into an expression and get the value of the last expression as a return value? I tried that and it worked. And of course, if it works it's good (standards? what are those?)

Then I tried using the code with a different C compiler; oops!

Anyway, this GNU C feature doesn't have as much of an impact as you might think.


I see two issues with the ternary operator. One, the syntax is much less readable, and two, the consequent and alternate are both single expressions, so you can't do something like:

x = if(something) { a = foo(); baz(a); } else { b = bar(); baz(b); }


It should work in GNU C:

  x = (something) ? ({ a = foo(); baz(a) }) : ({ b = bar(); baz(b); });
However, since all your forms are actually expression statements, we can happily just use the ISO C comma operator:

  x = (something) ? (a = foo(), baz(a)) : (b = bar(); baz(b));
If C provided operators for iteration, selection and for binding some variables over a scope (that scope consisting of an expression), everything would be cool. E.g. fantasy while loop:

  x = (< y 0) ?? y++ : y;   // evaluate y++ while (< y 0), then yield y.
Variable binding:

  x = let (int x = 3, double y = 3.0) : (x++, x*y);
The problem is that some things can only be done with statements.

The ternary operator is not actually lacking anything; with the comma operator, multiple expressions can be evaluated. What's lacking is the vocabulary of what those expressions can do.


I've always read ternary statements to myself as a question.

  some_condition ? this : that
some_condition? then this, otherwise that

Typing this, I realize how hard it is to explain without speaking it :)


Is the color red? If so, this; otherwise, that.

It's a little tricky, because in the above, the `if` keyword appears after the question mark.

Is the color red? Yes-- this: no-- that.

Trying to make a parsimonious English sentence while maintaining the syntax elements : P


> some_condition ? this : that

Just read it as:

  IF some_condition THEN this ELSE that


You compute baz in both branches, you can refactor.

    x = baz(if (something) foo() else bar())


If you insist on the assignments:

x = something ? ((a=foo()), baz(a)) : ((b = bar()), baz(b));

Otherwise:

x = something ? baz(foo()) : baz(bar());


a and b have to be defined before the statement containing the terniary expression here. And the point is that you can embed arbitrary multi-statement logic in your if expressions in Rust in the same way you'd do it anywhere else.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: