Let x, let mut x, move x in generics


#1

closures have kinda weird semantics, we have by-ref closures and by-move closures. now what’s even weirder is those are implied in the type system, but there’s no way to actually describe them in generics.

the ref and move bindings are part of the type, but you don’t see them in the type.

we should fix that.


#2

Do you have a sketch of what you’d like to see for various closures?


#3

Maybe something like this?

fn foo() {
    let x = 1;
    let y: Fn() -> i32 + let x = || x;
}

I don’t know how the syntax for this should look… this is just something I noticed after talking about [see my other recent posts]

I guess “move” doesn’t work like this hmm…


#4

It’s not clear to me how you would use this “in generics” though. Why do you ever need to know the mode in which something was captured from generic code? FnOnce, Fn, and FnMut tell how you can call it, and the lifetime tells how long it can live if it’s borrowed anything.

(If that justification is in your other posts, please summarize or link what you’re talking about…)


#5

you see, I don’t really like that. I want more power in the type system, and currently it seems like you need to go out of your way to make some things work… while I think they should just work.


#6

That’s what I’m asking – what exactly is it that you want to just work? Why does it fail, and how would your proposal make it better?


#7

I feel like this (let, let mut, move) is a prerequisite for my other suggestions (variables and fields in generics/I don’t have better words for it), so that’s why I’m talking about it.


#8

@Soni have you read this blog post of mine? It lays out how the move keyword, in fact, suffices to give you 100% precise capture clauses.

Our original thinking was basically this: if you don’t write move, we “Do The Right Thing” to the extent we can. When you write move, you have complete control.

The problems I personally encounter with closures fall into a few categories:

  • Partial path capture (addressed by RFC 2229)
  • The ability to make “move” with a scope – e.g., if you make a move closure, you effectively get a closure that is completely detached from the creating scope. Often, though, I want something that is only partially detached – e.g., I want to take ownership of local variables declared within the loop, but take references to the longer lived variables that are outside the loop (because the closure may escape the loop, but not the fn as a whole).

I think there may be room for capture clauses but I’d like to (at minimum) think carefully about which use cases we are trying to address with them and make sure we’re getting everything we need.


#9

I want dependent(?) types I guess?


#10

First off, I know of no production-ready programming language with dependent types at this time. Haskell seems close(ish), but whether the Haskell library ecosystem is production-ready is a discussion on its own. And regular Haskell program compile times are already fairly legendary.

Second, I can’t prove this but it seems like the more you validate using the type system, the more compile times go up. Regardless of how that scales*, for most projects there is often a balance to strike between writing and maintaining the type-level code** as well as the longer compile times*** on the one hand, vs the extra safety guarantees it really adds for your use case on the other. At some point the extra effort required is not worth it anymore.

*I suspect superlinearly i.e. not favorably, because that is usually the case for interesting algorithms

**Type level code is more difficult to comprehend and thus maintain than regular code, at least at this point in time

***Longer compile times mean a longer edit-feedback cycle. Usually it’s desirable to shorten this as much as possible, in any language.


#11

I want safe JoinGuards?