tim: Tim with short hair, smiling, wearing a black jacket over a white T-shirt (working)
[personal profile] tim
I closed #2735, which had to do with how
let _ = e;


can have different semantics in Rust. As you might guess, "_" effectively means "I'm never going to use this, so just give it a name I can't refer to." (Left-hand sides of let decls, by the way, can be any irrefutable pattern.) So why would they be different?

e might have a struct type (structs are the new classes) that has a destructor. So in the first case, the code generator looks at the decl and says "we're binding e to something, so run the destructor when that something goes out of scope". So if you actually had:
let _ = e;

e's destructor would run, presumably having observable effects, after "Hello" got printed out. On the other hand, with just e; the code generator sees that you're not doing anything with the result of e;, and runs the destructor immediately after evaluating e.

I changed it by adding a special case that checks the pattern on the left-hand side for "_"-ness. It feels a little odd to have this special case, but weirder to have these two forms behave differently.

Mainly, though, I'm still working on removing non-exhaustive matches from the Rust codebase. I started compiling a list of all the non-exhaustive matches I removed, and classifying them roughly. The hope is that this will be evidence that we can actually use to figure what, if any, language features to add to make it easier and safer to express the knowledge that's in your head (but not communicated to the compiler) when you wrote a match check before. (match is the new alt. Oh, syntax changes.)

(no subject)

Date: 2012-08-09 03:27 am (UTC)
juli: hill, guardrail, bright blue sky (Default)
From: [personal profile] juli
A common idiom in C++, which may not be anything at all like relevant in Rust, is to use scoped constructor/destructor behavior to acquire a lock within a block of code. I don't recall whether Rust has inbuilt synchronization, but you may find people doing things like that, if there's not some better mechanism on offer. Locks aren't the only things, but by far the most common. It seems like special-casing on _ seems wrong — presumably you have some defined semantics around when constructors and destructors run, right?

(no subject)

Date: 2012-08-11 04:53 pm (UTC)
juli: hill, guardrail, bright blue sky (Default)
From: [personal profile] juli
It's RAII, I guess. I've always thought of it as different, but it turns out that I'm wrong about that. The reason I think of it as different is that the thing which handles the scoping is not itself used. That is, you really do want your scoped-lock object to be named _. It has no meaningful operations. (For some reason in my big C++ codebase I decided to add some operations on it, presumably because I'm some kind of jerk.) With automatic reference-counting using the RAII model, obviously the smart pointer (or whatever) is something the programmer uses directly. In one case you're sort of faking syntax the language lacks (using scope for mutual exclusion), and in the other case you're actually using a resource which happens to be bounded by the scope in a meaningful way to fake semantics the language lacks (a reference-counted, garbage collected, etc., heap.)

(no subject)

Date: 2012-08-16 07:18 am (UTC)
luinied: Doing stretches and really not thinking about what's going on in the other room. (idea)
From: [personal profile] luinied
...I meant to reply to this when it was posted, but then I had travel and somehow forgot? Anyway: it seems to me that you might define the scope of _ to always be trivial - that is, anything that happens at the end of its scope happens right after everything that happens at the beginning of its scope. Which makes sense to me, because _ is the variable you're swearing you don't actually ever want to use, and it seems like it would give consistent behavior in all the cases you're thinking of. Does this sound like a sensible solution to you?

(no subject)

Date: 2012-08-17 09:23 pm (UTC)
luinied: How civilized! (academic)
From: [personal profile] luinied
Maybe I'm missing something, but I think you'd:
  1. evaluate the right-hand side,
  2. bind its .1 component to a and its .2.2 component to b (this isn't something that can somehow have side-effects, is it?), and then
  3. run the destructors on the .2.1 and .3 components (in that order if destructors otherwise fire queue style, in the opposite order if they fire stack style), because these parts were "bound" to _, which gives them a trivial scope by virtue of _ being the programmer's declaration that they don't care about whatever this would be bound to.
You are going to go against someone's intuition no matter how you do this. Functional programmers would expect cases like the one you first posted to work the same way, while C++ programmers aren't used to _ being special and would expect it to behave exactly like any other variable. I obviously have my own bias in siding with the functional programmers, but if that's the way you go with Rust, I think that declaring, in the part of the language definition that talks about _, that if effectively has a trivial, immediately-closed scope is a sensible way to do it. (But, again, I don't know Rust like you do, so maybe there are still ambiguities I'm not seeing.)


tim: Tim with short hair, smiling, wearing a black jacket over a white T-shirt (Default)
Tim Chevalier

October 2017

8910 11121314
15 161718192021

Most Popular Tags

Page Summary

Style Credit

Expand Cut Tags

No cut tags