Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

So I'm no Rust expert but when I saw:

    let _ = Foo;
my immeidate thought was "this is going to get cleaned up immediately", which turns out to be the case. If you come from a language where braces define scopes and variables only get cleaned up when a variable goes out of scope, Rust's behavior may be surprising but it's consistent and correct. The object is immediately out of scope.

Three thoughts:

1. Up until a certain point, being able to develop the language without updating a specification is useful. When you reach a certain level of maturity, you need a spec. I think Rust is nearing that inflection point;

2. A spec will make it harder to make breaking changes. That's a good thing. I can't but help think of Python 3. The seal was broken and every point release (it seems) makes breaking changes. That's not a good thing; and

3. A spec doesn't prevent undefined behavior. C/C++ have lots of undefined behavior. Sometimes later spec revisions will address this. Sometimes not.



> If you come from a language where braces define scopes [...] Rust's behavior may be surprising but it's consistent and correct. The object is immediately out of scope.

As someone from a language background where braces define scopes the behavior currently demonstrated is actually perfectly in alignment with my expectation. The variable is never even entering the block scope defined by the function boundary. The variable was created within the temporary scope of the line and then is destroyed because no symbols exiting that line were binding it to a value. Aka - the variable never formally entered the symbol table for that block.

Maybe a standard of some kind encoding these changes would be helpful but I did live through the 20 billion different C compiler era and, tbh, if you weren't trying to be fancy you could usually get shit done - standards require a large upfront and continual maintenance cost and I'd prefer to see some actually dangerous ambiguous operations before the community invested in one.


`_` in a lot of languages is called a discard or wildcard. It comes from Rust's ML heritage, and that's essentially what it does in ML too. C# has something similar. It's not unexpected at all, especially given how the syntactic feature is described.


Mostly, the ANSI 89 C standard ensured portability, and long-term core functionality of ecosystems through bootstrap compilers etc. Arguably, the standards compliance was indirectly responsible for the gcc becoming popular.

The issue with modern C++, was users that began to mix core features with externally defined popular abstract Standard Template Library feature implementations. i.e. while an attempt was made to bring the compiler features into global harmony. The outcome was an ever spiraling complexity with conflicting use-cases, and multiple valid justifications in every case.

Rust will likely end up like NodeJS/PHP/C++ ecosystems... not simply because of llvm quality issues, but rather it is human nature to check-off all prerequisites for a "Second-system effect".

YC has shown some of the proponents are irrationally fanatical, and unfortunately all too predictable... one may append the standard AstroTurf opinions that deny reality below... =3


But in this it is not.

   let _tmp = Foo;  
The confusing bit is renaming the object from _tmp to _ changes the rules. I assume that in rust '_' is not an actual name, but a pattern to be matched, but even that the actual rule applied is not obvious (does it match everything? Only the lifetime of objects matched with a name is extended?)


Correct, _ is a pattern that does not bind to any name. While the implications of that may not be simple, the description certainly is.

Names that start with underscores are still names, and work like any other name. The “unused variable” into can be suppressed with a leading underscore like any other name, but that’s purely about the lint and not semantics.


I independently came up with a similar hack in TXR Lisp. When a variable is named by a gensym, unused warnings are suppressed. Gensyms are almost always symbols generated by macros. Machine generated code often has cases where variables are unused.

This design decision is a trade off. Any situation in which a variable is unused is potentially a bug, even if the variable is in code written by a macro and named by a generated symbol.

But it is a burden on macro writers to ensure that all possible cases of general code are free of unused variable warnings.


So the issue really is that the match-and-discard wildcard uses a spelling that would otherwise be a valid identifier name causing an ambiguity to the reader, if not to the compiler.

In retrospect using a different character (say '*') would have been better, clueing the reader that something different was happening; but I guess there is long history of this use of _ in ML languages.


I mean, it’s never a valid identifier, and that is spelled out in the reference too.


of course it isn't, it acts as a keyword, but it looks like a valid identifier. As this difference doesn't matter 99% of the time, it can be be easily overlooked, becoming a pitfall the 1% that matters. Of course it too late to change it, but pointing it is a must. I understand rust has a linter pass specifically to catch this sort of issues.


Those are two different things.

- A _ prefix tells the compiler not to warn you a variable isn't used.

- A _ variable name is a reserved symbol to say something cannot be used. Trying to do so will cause a compiler error.

So they're scoped differently.

Consider this Go snippet:

    func A() { fmt.Println("A") }
    func B() { fmt.Println("B") }
    func main() {
      var B = A;
      B()
    }
What function gets called? It's A but you can reasonably make a case for B given the capitalization of Go function names and variables. My point is that scoping and resolution rules and many other aspects of a language can be viewed as intuitive (or not) based simply on what you're used to. Even then they can be arbitrary.


In a language where functions are in the same namespace as variables (like the majority of languages), I would expect A, in a lisp-2 I would expect B.

But I don't see how's that relevant, this is not about the scope of bindings, but about the lifetime of objects.

As Steve explained in a sibling comment, _ is in fact not a variable name, and doesn't extend the lifetime of the object. The semantic is perfectly reasonable and I'm sure it has a good reason to be that way, but the difference between two similarly looking syntaxes is surprising and violates the principle of least astonishment.

FWIW, C++ temporary lifetime extensions are also surprising in many cases.

[Note: I don't know if rust is defined in term of temporary lifetime extensions, it is just a way to understand it for a c++ programmer like me]


The definitions are a bit different than in C++ but the overall effect is the same: https://doc.rust-lang.org/stable/reference/expressions.html#...


If B is called, and the language is otherwise reasonable, then it must mean that there's an A variable in the program which is not showin in the above code.

The language must have separate namespaces for functions and variables. But in that case var B = A must be resolving A in the variable namespace; it does not refer to the function A. The local B variable is initialized from some nonlocal A variable, and neither of those are related to the A or B functions, nor have any interaction with the B () call which ignores the variable space, looking up in the function space.




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

Search: