--- title: "Design meeting 2024-08-21: Ergonomic reference counting" tags: ["T-lang", "design-meeting", "minutes"] date: 2024-08-21 discussion: https://rust-lang.zulipchat.com/#narrow/stream/410673-t-lang.2Fmeetings/topic/Design.20meeting.202024-08-21 url: https://hackmd.io/WXg2ds7dSKWA2RlsSD9udg --- - Feature Name: `use` - Start Date: 2024-07-20 - RFC PR: [rust-lang/rfcs#3680](https://github.com/rust-lang/rfcs/pull/3680) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary [summary]: #summary Provide a feature to simplify performing lightweight clones (such as of `Arc`/`Rc`), particularly cloning them into closures or async blocks, while still keeping such cloning visible and explicit. # Motivation [motivation]: #motivation A very common source of friction in asynchronous or multithreaded Rust programming is having to clone various `Arc<T>` reference-counted objects into an async block or task. This is particularly common when spawning a closure as a thread, or spawning an async block as a task. Common patterns for doing so include: ```rust // Use new names throughout the block let new_x = x.clone(); let new_y = y.clone(); spawn(async move { func1(new_x).await; func2(new_y).await; }); // Introduce a scope to perform the clones in { let x = x.clone(); let y = y.clone(); spawn(async move { func1(x).await; func2(y).await; }); } // Introduce a scope to perform the clones in, inside the call spawn({ let x = x.clone(); let y = y.clone(); async move { func1(x).await; func2(y).await; } }); ``` All of these patterns introduce noise every time the program wants to spawn a thread or task, or otherwise clone an object into a closure or async block. Feedback on Rust regularly brings up this friction, seeking a simpler solution. In addition, Rust developers trying to avoid heavyweight clones will sometimes suggest eschewing invocations of `obj.clone()` in favor of writing `Arc::clone(&obj)` explicitly, to mark the call explicitly as a lightweight clone, at the cost of syntactic salt. This RFC proposes a syntax that can *only* make a lightweight clone, while still using a simple postfix syntax. In some cases, people ask for fully *automatic* cloning, requiring no visible indication at the point of the clone. However, Rust has long attempted to keep user-provided code visible, such as by not providing copy constructors. Rust users regularly provide feedback on this point as well, asking for clones to not become implicit, or otherwise confirming that they appreciate the absence of copy constructors. This RFC proposes solutions to *minimize* the syntactic weight of lightweight-cloning objects, particularly cloning objects into a closure or async block, while still keeping an indication of this operation. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation When working with objects that support lightweight cloning, such as `Rc` or `Arc`, you can get an additional clone (a new "use") of the object by invoking `.use`: ```rust let obj: Arc<LargeComplexObject> = new_large_complex_object(); some_function(obj.use); // Pass a separate use of the object to `some_function` obj.method(); // The object is still owned afterwards ``` If you want to create a closure or async block that captures new uses of such objects, you can put the `use` keyword on the closure or async block, similar to the `move` keyword. ```rust let obj: Arc<LargeComplexObject> = new_large_complex_object(); let map: Arc<AnotherObject> = new_mapping(); std::thread::spawn(use || map.insert(42, func(obj))); task::spawn(async use { op(map, obj).await }); another_func(obj, map); ``` (Note that `use` and `move` are mutually exclusive.) `.use` supports chaining, so in particular it works when calling a method that would otherwise consume `self`: ```rust obj.use.consume(); obj.method(); ``` Calling `x.use` requires that the type of `x` implement the `Use` trait. This trait identifies types whose clone implementation is lightweight, such as reference-counted types. Various types in the standard library implement `Use`. You can implement this trait for your own types, if they meet the requirements for being lightweight to clone. (See the [reference-level explanation][reference-level-explanation] for the requirements.) ```rust impl Use for MyType {} ``` # Reference-level explanation [reference-level-explanation]: #reference-level-explanation ## The `Use` trait ```rust /// Trait for objects whose clone impl is lightweight (e.g. reference-counted) /// /// Cloning an object implementing this trait should in general: /// - be O(1) (constant) time regardless of the amount of data managed by the object, /// - not require a memory allocation, /// - not require copying more than roughly 64 bytes (a typical cache line size), /// - not block, /// - not have any semantic side effects (e.g. allocating a file descriptor), and /// - not have overhead larger than a couple of atomic operations. /// /// The `Use` trait does not provide a method; instead, it indicates that /// `Clone::clone` is lightweight, and allows the use of the `.use` syntax. trait Use: Clone {} ``` This trait should be implemented for anything in the standard library that meets these criteria. Some notable types in the standard library that implement `Use`: - `std::sync::Arc` - `std::sync::Weak` - `std::rc::Rc` - `std::rc::Weak` - `std::sync::mpsc::Sender` and `std::sync::mpsc::SyncSender` - Tuples of types whose components all implement `Use` - `Option<T>` where `T: Use` - `Result<T, E>` where `T: Use` and `E: Use` Some notable types that implement `Clone` but should *not* implement `Use`: arrays, `String`, `Box`, `Vec`, `HashMap`, and `BTreeMap`. We may want to add a clippy or rustc lint (e.g. `expensive_use`) for implementations of `Use` on an excessively large type, or a type whose `clone` implementation seems to be obviously breaking the intended constraints on the `Use` trait. Such a lint would be best-effort only, and could always be marked as `allow` by a crate, but could help to discourage such implementations. We may want to add a clippy or rustc lint for calls to `.clone()` that could use `.use` instead. This would help the remaining calls to `.clone()` stand out as "expensive" clones. (Such a lint would need to take MSRV into account before making such a suggestion; clippy already has such a mechanism and rustc may gain one in the future.) ## The implementation and optimization of `.use` An expression `x.use`, where `x` has type `T`, requires that `T: Use`. However, `x.use` does not always invoke `Clone::clone(x)`; in some cases the compiler can optimize away a use. If `x` is statically known to be dead, the compiler will move `x` rather than using it. This allows functions to write `x.use` without concern for whether it's the last usage of `x` (e.g. when passing `x.use` to a series of functions). Much like a trailing comma, this allows every usage to be symmetric, making it easy to compare uses or add more uses afterwards. (This optimization also means we should generally not lint on a final use of `x.use`, such as we currently do with the clippy lint `redundant_clone`.) If `x` is not statically known to be dead, but *all* of the following conditions are met, the compiler *may* elide an `x.use` and use `&x` instead: - The compiler can statically see that `x` outlives the result of `x.use`, - The compiler can statically see that the result of `x.use` is only accessed via shared reference (including methods with `&self`, dereferences via `Deref`, other invocations of `.use`, `use ||` closures, or `async use` blocks). Effectively, these conditions mean that the user could theoretically have refactored the code to use `&x` rather than `x.use`. An example of these elisions: ```rust fn f(obj: Arc<Object>) { g(obj.use); // `g` takes `Arc<Object>` h(use || obj.method()); // `Object::method` here takes `&self` } fn main() { let obj = Arc::new(Object::new()); f(obj.use); f(obj.use); f(obj.use); g(obj.use); } ``` If every invocation of `.use` or `use ||` here resulted in a call to `Clone::clone`, this program would call `Clone::clone` 10 times (and have 11 `Arc`s to drop); however, the compiler can elide all the uses in `main` and have `f` work with the original Arc, resulting in only 6 calls to `Clone::clone` (and only 7 `Arc`s to drop). When an object is used repeatedly, such as in a loop, this can result in many elisions over the course of the program, and lower contention for atomics. If a user has an unusual `Use` type for which they wish to avoid these potential elisions, they can call `.clone()` directly. At any time, we could potentially instrument the compiler to detect the number of elided calls to `Clone::clone` in a crater run, to demonstrate the value of this optimization. ## `use ||` closures and `async use` blocks A closure can be written as `use |args| ...`, and an async block can be written as `async use { ... }`, analogous to the use of `move`. (Note that `use` and `move` are mutually exclusive.) For any object referenced within the closure or block that a `move` closure/block would move, `use` will `.use` that object and have the closure own the new use of the object. # Drawbacks [drawbacks]: #drawbacks This adds language surface area. While this still makes lightweight clones visible, it makes them *less* visible. (See "Rationale and alternatives".) Users may misuse this by implementing `Use` for a type that doesn't meet the requirements. Not all of the requirements can be checked by the compiler. While this is still *safe*, it may result in types that violate user's expectations. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives We could do nothing, and require people to continue calling `.clone()` and introducing new bindings for closures and async blocks. Rather than specifically supporting *lightweight* clones, we could add a syntax for closures and async blocks to perform *any* clones (e.g. `async clone` / `clone ||`). This would additionally allow expensive clones (such as `String`/`Vec`). However, we've had many requests to distinguish between expensive and lightweight clones, as well as ecosystem conventions attempting to make such distinctions (e.g. past guidance to write `Arc::clone`/`Rc::clone` explicitly). Having a syntax that only permits lightweight clones would allow users to confidently use that syntax without worrying about an unexpectedly expensive operation. We can then provide ways to perform the expensive clones explicitly, such as the `use(x = x.clone())` syntax suggested in [future possibilities][future-possibilities]. There are myriad names we could use for the trait, invocation syntax, and closure / async block syntax. An ideal name for this needs to convey the semantic of taking an additional reference to an object, without copying or duplicating the object. Other names proposed for this mechanism include `Claim`. We could use a name that directly references referencing counting; for instance, `AddRef`. However, that would be confusing for applications using this with objects managed by something other than reference counting, such as RCU or hazard pointers, or with (small) objects being copied. Rather than having the method `Clone::clone`, we could have a method `Use::do_use` and have the compiler call that. We could prevent users from overriding that method implementation, so that it always calls `Clone::clone`; however, having a separate `do_use` method would allow users to call `.do_use()` (when wanting to avoid the elision of `.use`) while knowing they can't invoke an expensive clone operation. This does not seem worth the additional complexity, since most users should invoke `.use` directly. Rather than using the special syntax `.use`, we could use an ordinary trait method and invoke that method directly (e.g. `.claim()`). The syntax provided for closures and async blocks could likewise always invoke that method. This would not be compatible with adding smarter semantics such as eliding uses, however. In addition, `.use` can be used in all editions of Rust (because `use` is a reserved keyword), while a new trait with a method like `.claim()` would require an import in existing editions and could only become part of the prelude in a future edition. Rather than attaching this behavior to types (e.g. `Arc` and `Rc`), we could attach this behavior to *bindings*. For instance, we could have `let somekeyword x = Arc::new(...);` and make it easy to clone such bindings into closures and async blocks. However, this would add additional noise to every such binding, when users are likely to want this behavior for *every* binding of a given type (e.g. every `Arc`). In addition, this would require adding such an annotation to struct fields and similar. We could use an ordinary trait method *and* add a new set of semantics attached to that trait method, such that the compiler is allowed to elide calls to the trait method. This would avoid the introduction of new language syntax for `.use`, at the cost of making something that looks like an ordinary method call have semantics beyond those normally associated with a method call. Rather than having a single keyword in `use ||` or `async use`, we could require naming every individual object being used (e.g. `use(x, y) ||`). There's precedent for this kind of explicit capture syntax in other languages (and having it as an available *option* is in the [future possibilities][future-possibilities] section). However, requiring a list of every object used adds overhead to closures and async blocks throughout a program. This RFC proposes that the single keyword `use` suffices to indicate that lightweight cloning will take place. Rather than having `Use` act as `Clone` and go from `&self` to `Self`, we could translate through a trait like `ToOwned`. This would allow using `Use` when owned values have a different type, such as `&MyType -> SmartPtr<MyType>`. However, this would also add complexity to the common case. We could omit the elision optimizations, and have `.use` *always* call `Clone::clone` unconditionally. This would be slightly simpler, but would add unnecessary overhead, and would encourage users to micro-optimize their code by arranging to omit calls to `.use`. The elision optimizations encourage users to always call `.use`. In the elision optimizations, we could potentially allow the last usage of the result of `x.use` to take ownership of it, and the compiler could insert a `.use` at that point. However, at that point the elision would not have resulted in any fewer calls to `.use`, so this does not seem worth the extra complexity. Rather than adding elision behavior for `.use`, we could add elision behavior for specifically designated `Clone` implementations (e.g. with an attribute). However, this would leave users unable to make an *un*-elided call to `clone`. And if we added a mechanism to bypass this elision, the result would likely have comparable complexity to the addition of a separate trait. Nonetheless, there's potential for possible optimization here, and that optimization might benefit expensive clones as well (e.g. eliding the clone of a `String` if the `String` being cloned is statically known to be dead). We should explore potential optimizations here. Rather than making the elision optimizations optional and allowing for future improvements to them, we could mandate those optimizations and specify the exact elision rules. This would, however, constrain our ability to improve elision in the future, as well as requiring full implementation of those elision rules before shipping the feature. This would not be the first Rust mechanism whose implementation is subject to change, and leaving it flexible allows the Rust compiler to improve in the future. # Prior art [prior-art]: #prior-art Many languages have built-in reference counting, and automatically manage reference counts when copying or passing around objects. `.use` provides a simple and efficient way for Rust to manage reference counts. Some languages have copy constructors, allowing arbitrary code to run when copying an object. Such languages can manage reference-counted smart pointers implicitly. Rust already has other uses of postfix syntax, notably `.await` and `?`. In particular, `.await` provides precedent for the use of `.keyword`. # Unresolved questions [unresolved-questions]: #unresolved-questions How should we handle borrows within `use` closures and `async use` blocks? The goal would be to borrow thing for which a borrow suffices, and `.use` things that need to be `use`d; the primary benefit of this would be the ability to use a `use` closure or `async use` block with, for instance, a large array, if the large array only needs to be borrowed. On the other hand, if it's *possible* to borrow the large array, it may well be possible to borrow other objects as well, in which case that closure/block may not need `use`; thus, we may not need a solution for this use case, or at least we may not need a solution urgently and we can wait for the future possibility of `use(...)` syntax to handle the array by name. Are there use cases for using something like `ToOwned` rather than always going from `&self` to `Self`? Would any smart pointers need that? # Future possibilities [future-possibilities]: #future-possibilities We could implement `Use` for small arrays (e.g. arrays smaller than 64 bytes). We could allow `.use` in struct construction without duplicating the name of a field. For instance: ```rust let s = SomeStruct { field, field2.use, field3.use }; /// This expands to: let s = SomeStruct { field: field, field2: field2.use, field3: field3.use, }; ``` We could extend the `use` syntax on closures and async blocks to support naming specific objects to use: ```rust use(x) || { ... } async use(x) { ... } ``` We could further extend the `use` syntax to support an explicit capture list of named expressions: ```rust use(x = x.method(), y) || { ... } // `..` additionally captures everything that a plain `use` would async use(x = &obj, move y, ..) ``` We could consider providing a syntax to make invocations like `func(a.use, b.use, c.use)` less verbose. In many cases, such a function could accept a reference and call `.use` itself if needed, but we should evaluate whether there are use cases not covered by that. --- # Discussion ## Attendance - People: TC, scottmcm, nikomatsakis, Josh, tmandry, Santiago, Xiang, Kevin Boos, Jonathan Kelly, Urgau, Jane Losare-Lusby (yaahc) ## Meeting roles - Minutes, driver: TC ## The overall story nikomatsakis: The RFC doesn't quite capture the "Rust ownership story" once we're done. I think we should review all the details. I see it as something like this (after this RFC) * Rust values can be broken down into * all values * "cloneable" values, which has two (non-disjoint) categories * "usable" (can be cheaply, transparently cloned) * "memcpy"'able (`Copy`) * (the interaction of `Use, Copy` is a meaningful subset under this proposal) This implies * When you access a place "by value" (`x`), this takes ownership of the value within * If the value is `Use` *and* `Copy`, the old value remains usable * Otherwise, it does not (either via warn or error) * When you "use" a place (`x.use`), this will allow `x` to continue being used * This will copy/clone if it must * Only possible for `Use` values where cloning is cheap & transparent * When you explicitly clone a place (`x.clone()`), `x` always goes on being used, but * cost / semantics can vary, might not be cheap, might not be transparent I am assuming here that we add some lints and that we deprecate `move` closures, I don't see the value in them, but I covered those things specifically elsewhere. Josh: I don't think "`Use` *and* `Copy`" is true in that first bullet? Otherwise, +1. NM: E.g., I think that `Cell<T>` should be `Copy` if `T: Copy` but not `Use`. NM: The main thing I'm saying is that `Copy` should imply `memcopy`-able, and `use` means "cheap and transparent". NM: I'm not saying this RFC should add `Copy` to things, but we should leave space for it. NM: I'd like to make the mental model easier for users, and I'm not sure the current framing does that yet. Josh: Proposal: add an item to future work about potentially using lints to improve cases of things that are `Copy` and expensive. https://github.com/rust-lang/rfcs/pull/3680/commits/7825d315e74d5339b39fe826f9375515761bc723 ## Iterators and `Use` scottmcm: Should iterators by `Use`? nikomatsakis: I think no, but it is because of this. We've said historically that this may be confusing: ```rust let mut v = x.iter(); let mut w = v; // independent iterators v.next(); w.next(); // independent iterators ``` Under RFC as written: ```rust let mut v = x.iter(); let mut w = v.use; // <-- would be required v.next(); w.next(); ``` ...and I don't think there's a problem with that. NM: I'd like to keep space for: ```rust let mut v = x.iter(); let mut w = v; // <-- automatic v.next(); w.next(); ``` ...so I would rather we NOT make iterators `Use` at present, but I would want to make them `Copy`. (Iterators are not "transparent", morally, even though they are technically.) ## Criteria for cloning nikomatsakis: I think it's important that we identify three key criteria for cloning * Cheap * Transparent * Infallible The full list of bullets is a good expansion on that list, but it is missing in particular the *transparent* quality. By transparent I mean: anything you do on the clone, you could as well do on the original. This is what allows us to make `x.use` be transparently compiled to access to `x`. By infallible I mean that it will not panic etc. I do not mean "cannot abort", since e.g. arc can abort. But it should generally. scottmcm: `From` <https://doc.rust-lang.org/std/convert/trait.From.html#when-to-implement-from> uses "value-preserving", so if you think of cloning as `T: From <&T>`, it probably wants to follow all those entries from the `From` list. TC: For "transparent", I'd be tempted to call this "indifferentiable" or "indistinguishable", as that's what we're getting at (that you can't differentiate or distinguish the original from the cloned version). NM: it's not exactly sure that a clone of `Arc` is indistinguishable from the original (`ptr_eq`) but it's basically true (and `Eq::eq` impl doesn't care). Josh: Not sure if we can guarantee "infallible" in all cases? NM: The property I'm going for is "you can keep using the original" -- we are relying on that when we say we may elide a `use`, after all. It's kind of "indistinguishable" from the original (At least for `&`-references) Josh: +1 for adding "semantically indistinguishable"; let's hammer out a definition after the meeting. NM: E.g., this could implement `Use + Copy`: ```rust struct Fibonacci { n: u32, // this doesn't impact API, can't tell if it's cloned or not cached_result: Cell<Option<u32>>, } impl Fibonacci { fn get(&self) -> u32 { if self.cached_result.get() { return self.cached_result.get().unwrap(); } self.cached_result.set(self.compute()) } } impl Copy for Fibonacci { } impl Use for Fibonacci { // ok because it's hidden } ``` TM: How does it allow us to implement `Copy` for iterators? NM: First, we could always impl `Copy` for iterators; we haven't done it because of a potential footgun. NM: By itself, the RFC doesn't solve that, but this RFC plus another step does. I would like to make `Use` be the trait for "you can go on using this" -- in this RFC, it may require an explicit `x.use` or it may not (if the type is also `Copy`). ```rust // if we added this impl Copy for Range<u32> {} // then you could write this today with no warnings let mut x = (0..3); let y = x; x.next(); // modifies `x` ``` If we added a lint that says "copying a `!Use` thing" generates a lint: = ```rust // if we added this impl Copy for Range<u32> {} // then you could write this today with no warnings let x = (0..3); let y = x; // LINT warning because `Range<u32>: ?Use` x.next(); ``` tmandry: Then you would be expected to write, e.g., `x.clone()` (even though its type is Copy). Maybe a better example: ```rust let mut v = (0..5); let w = use || read(v); // does this see the result of `next`? v.next(); w(); ``` ## Long-term direction nikomatsakis: There are some parts of the motivation for this RFC I don't totally agree with, but it's more about "inflection" than the "details". e.g. this paragraph > In some cases, people ask for fully *automatic* cloning, requiring no visible indication at the point of the clone. However, Rust has long attempted to keep user-provided code visible, such as by not providing copy constructors. Rust users regularly provide feedback on this point as well, asking for clones to not become implicit, or otherwise confirming that they appreciate the absence of copy constructors. It's kind of a nit, but I'd rather this read as something like * Rust has historically hit a balance with copy/clone but it has flaws. * Some Rust users would like to see fully automatic clones but others feel that would go too far. * This RFC preserves explicit syntax but makes that syntax lightweight and allows for generating more efficient code. Josh: Effectively, you're asking for this RFC to more explicitly treat this as a tension? nikomatsakis: I think my concern is that, if I "affirm" this RFC with this motivation as written, I feel I am affirming that we don't want fully automated cloning ever. I don't agree with that. Josh: My intent was more that affirm this RFC affirms that *some users* would like there to never be fully automated cloning. That doesn't mean we can't decide to do it *anyway*, it means we acknowledge that some users don't want it. nikomatsakis: Yes, I'm happy with affirming that some users don't want it, and therefore we are not doing it now. It's just that I somehow didn't get that from the way it was written. Josh: I can attempt to phrase this in a fashion that makes that more clear. A big part of my phrasing was to attempt to make sure that users who *don't* want invisible cloning read the motivation and immediately take away that this RFC is *not doing that* and *not putting us on an inexorable path to doing that*. tmandry: I'm also wondering how many of the folks who don't want automatic cloning/copy constructors are fine with automatic *lightweight* cloning, i.e. that described in the docs of the Use trait. Josh: Proposal: that question doesn't seem like a blocker for this RFC? tmandry: I don't think it's a blocker. It does seem likely to come up in an RFC thread, so I'm not sure how you want to deal with that. Josh: By repeating "that's not in this RFC and this RFC is not serving as precedent for it". :) Josh: @nikomatsakis I've rewritten this paragraph of the RFC (in the RFC PR) to be more neutral towards future RFCs. Please let me know if the rewrite is sufficiently neutral; I'm trying to establish that the RFC neither precludes such future RFCs nor serves as any precedent/motivation for them. ## Design axioms nikomatsakis: I was thinking about design axioms here. I don't have a polished list. I think it'd be a useful exercise to try and talk through what the axioms are that are leading us here. Some intermediate thoughts I have (unordered, rough) * Pragmatic: we want to help people avoid footguns and get the code they want, we accept that this will involve some heuristics. * Unsurprising: we don't want to have surprising code executing; having explicit syntax is a way to ensure that (not the only way, perhaps, but definitely a way). Note that today's conflation of many different kinds of `clone` actually violates this axiom (it's surprising to many that `x.clone()` can be very cheap, for example). * Efficient: existing patterns aren't actually helping users get efficient code, both because they make expensive operations implicit (copying `[u8; 1024]`), fail to help users distinguish between cheap/expensive clones, and ultimately lead to *more* clones than may be needed. ## `move` / `use` mutually exclusive? TC: The document notes: > (Note that `use` and `move` are mutually exclusive.) > > For any object referenced within the closure or block that a `move` closure/block would move, `use` will `.use` that object and have the closure own the new use of the object. What's the intention for when a closure wants to capture things by move where not all of the things captured implement `Use`? Josh: Niko made a proposal on the RFC thread that might impact this... NM: There are two things going on. When you make a `use` closure, what does that mean? It could be reasonable for it to take ownership of all values that it uses, and it does so by moving the values that it can, and `use`ing the values that it must. TC: With that semantic, we're essentially deprecating `move || ()` in favor of `use || ()`. ## Collision with precise capturing `use`, RFC 3617 TC: The syntax proposed for closures, e.g. ```rust std::thread::spawn(use || map.insert(42, func(obj))); ``` ...potentially collides with anticipated extensions of the precise capturing `use` syntax. E.g., from that RFC: ```rust let f = use(a, ref b, ref mut c) || { .. }; ``` Essentially, the idea with precise capturing is that we could use `use(..)` to replace `move` with something that allows more precise control, i.e. *precise capturing* of not just generic parameters in opaque types (the subject of RFC 3617), but precise capturing of values in closures also. So what's in this proposed RFC seems a bit in tension with this as it gives a specific different semantic for `use` in these contexts. Perhaps there's a way to reconcile these? Josh: I think, rather than being in tension, these are aligned: any future-work extension adding `use(...)` syntax would provide a capture list, and would allow specifying what precisely to do with each item in the capture list. nikomatsakis: I agree with Josh, this is aligned in my view. TC: Per the answer in the section above, I agree that it aligns with the semantic that NM proposes where `use` supersedes `move`. tmandry: (Question about the syntax above.) TC: The other possibilities include: ```rust let f = use(a, ref b, ref mut c) || { .. }; // Other possibilities include: let f = use(move a, ref b, ref mut c) || { .. }; let f = use(use a, ref b, ref mut c) || { .. }; ``` tmandry: I wonder if we're adding mental overhead here. I feel there is some tension. NM: I wouldn't want to stabilize until we're settled on how this is going to feel. tmandry: I feel like we're likely to want this in some form, but it could impact syntax. Josh: We could always, at worst, use a different keyword. TC: Personally, I don't see a lot of tension here, and that there is symmetry, and that `use` here, for both, is probably is the best choice. tmandry: Agreed there's probably not a lot of tension here. It's just that we're adding things and still have `move`. Three different capture modes, plus we reuse the keyword for one of them to specify explicit captures. NM: I'd prefer to solve that by deemphasizing `move`. ## Should this be `#[marker]`? scottmcm: that's languished a bit, but overlapping implementations for this sound useful. (Like a future `impl<T: Copy> Use for Copy where const(???) {}`.) Josh: No objection; seems fine to add, assuming that permitting overlapping impls (and forbidding the possibility of adding methods) is the only net effect. scottmcm: Thinking about it now, it seems that it being `marker` now makes sense, but we could do it later. nikomatsakis: in terms of making this automatic, keep in mind things like iterators -- it's sometimes more about "how confusing will it be". +1 to marker trait in general though, just not sure it should be implicit. ## Size Limits scottmcm: the RFC says both > not require copying more than roughly 64 bytes (a typical cache line size), and that it'd be implemented for > Tuples of types whose components all implement Use The latter cannot uphold the former. Josh: The former was not meant to be a strict limit, and huge tuples don't seem especially common. Nothing breaks if you have a tuple or struct of a dozen Arc values, any more than if you had a dozen *separate* Arc values that a `use` closure/block calls `.use` on, and the latter will always work, so it seems fine to allow the former. nikomatsakis: my take here is that tuples are usually 1, 2, or 3, and arrays are often much bigger. It's a bit of a heuristic... but... scottmcm: Yeah, and to me heuristics are better a domain of lints, since `[u8; 4]` being `Use` is also completely fine. Maybe it's a future possibility once we can `where const { sizeof(T) < 100 }`... ## Could `Use` not be tied to Clone - and not exist as keywords on closure/async blocks at all? jkelleyrtp In the case of Rc, we purely want to increment a reference count such that the resulting Rc's `Drop` will decrement the reference count. Rust would still perform of memcpy of `T`, retaining its current behavior, but with extra user-logic for tracking. In this we case we don't need to use `Clone`, but rather simply `.hard_count += 1`. ```rust impl Use for Rc<T> { fn use(&self) { // called before memcpy self.hard_count += 1; } } impl<T> Use for T where T: Copy {} // or if we just overloaded copy itself impl Copy for Rc<T> { fn copy(&self) { self.hard_count += 1; } } ``` If we untied `Clone` from `use` we can still give Copy's super-powers to any types we want, but the semantics are different such that it's not an autoclone. We could be more aggressive with the implicitness of this behavior since it's not a *clone* and it's much harder to allocate, especially if the signature does not return an owned value. This would move our "convention" variants from "conventions" to "strict restrictions": no allocations, no clones IE move constructors without the constructor. NM: I'm not sure the difference between this and the proposal in this RFC is as large as stated. Josh: It seems the main decision here is whether we're willing to run implicit code without syntactic indication. I don't think this proposal necessarily makes it *more* likely that we'd be willing to run implicit code without syntactic indication NM: There is an interesting restriction here that the operations are done first and then it has to be memcopy-able. scottmcm: This may make it fundamentally unsafe though -- if it means your fields start getting memcopied that's fundamentally a new way to get double-drops. (The main meeting ended here. Discussion continued.) --- ## Use specific bindings in closures tmandry: If you have `x.use` in a closure it should work without `use ||`. Josh: The primary issue would be that you can't turn this into a `use` at the point of `.use`, as with `clone`. So the only way we could treat it correctly is to float it before the closure. I'm not sure if we *should* do that, but we could. TC: Let's write an example: ```rust let x = Arc::new(0u8); let f = || { let y = x.use; // <--- todo!(); } // This would have to implicitly become: let f = { let x = x.use; || { let y = x; // <--- `use` got floated outside the closure todo!(); } } ``` TC: Could we even always float it like that? What if we used it as a reference ahead of the `.use`? Josh: Counterexample for being able to float `x.use` like that: ```rust! let closure = || { x.do_mutate(); // This takes `&mut self` and should mutate the original func(x.use); // This makes a copy; floating it outside the closure would make mutation not affect original } ``` TC: (Capturing discussion, we considered this:) ```rust! let mut x = 0u8; let mut y = x.use; // Allowed? x += 1; assert_eq!(x, 1); assert_eq!(y, 0); // ? Does this break the indistinguishable property? ``` I.e., should we have `impl Use for u8 {}`? It almost seems like "cheaply copyable" may need a separate axis, and maybe a separate trait, than "indistinguishable", if we somehow wanted to notate `u8` as *cheaply copyable* but not *indistinguishable* after `clone`. TC: Confirming with NM later, he means for indistinguishable means something like "you need a `&mut` to distinguish it". So that's why `Cell<T>` shouldn't implement it, but types that contain `Cell` can as long as they preserve the property. ## What exactly is removable? scottmcm: We've said that `T: Copy` doesn't let us turn `x.clone()` into `*x` as an optimization, but that the standard library can do that with specialization. Should `T: Use` allow optimizations to turn `(&{x}).clone()` into just `x`? Is it a specialization trait? Can we add a "no lifetime funny business in impls" rule so it *could* soundly be used in specialization later? Or is it only `x.use` → `x` *syntactically* that's allowed? Also, is there any value in a `Drop`-like requirement that you can't add extra bounds on a `Use` impl? (I haven't thought through the implications of `Send` and such for `Arc` related to this, though.) A phrasing that was helpful in the discussion: Do we need to preserve `.use` down into the MIR, or can MIR see `<Clone + Use>::clone` and elide that? A couple of options that come to mind: - `x.use` can only elide into a move syntactically based on intra-function dataflow analysis. - `x.use` can become `{x}` or `*x` (depending if x is a reference) for a `Copy` type. - a mir optimization can see `<Clone + Use>::clone(&x)` and turn that into just `x` if it's never used later. - a mir optimization can see `<Copy + Use>::clone(x)` and turn that into `*x`. - either of those mir optimizations but looking at `UnOp::UseClone` instead. ## Potential Reference Issue (Clone-Into-Closures) jkelleyrtp: A reference issue for this could be https://github.com/rust-lang/rfcs/issues/2407 ## What "solves the problem?" jkelleyrtp 1. Adding per-var binding in closure/async block position ie `async use(a, b) || {}` might still be too much code noise 2. Having to `use` at function call boundaries could also be significant code noise 3. It's hard to replicate the powers of `Copy` in today's Rust with RAII types 4. Having a DerefOwned (like box) would be a similar approach but by using Deref semantics 5. When would you want to still use a `move` block over a `use` block? Especially if `use` optimizes to a `move` if it can. 6. I could just create a `let` binding such that the `use` is only a `move` for variables I care about 7. In that case `move` is kept around as a "tighter `use`" Worried that if we go "half way" it'll feel "grafted on" and/or awkward.`clone(a,b)` *might* be fine for Rust but, IMO, doesn't really push Rust over the "high level language" threshold in heavy closure/async code. More syntax is more complex, even if it does simplify code locally. ## Potential priort art in user-space solutions jkelleyrtp Our [Generational-Box](https://crates.io/crates/generational-box) crate gives `Copy` semantics to `T: !Copy` by offloading the RAII-drop to a separate "owner". The limit here is all boxes won't be dropped until the "owner" is dropped, which could potentially be unbounded. ## deprecating move closures in favor of use closures nikomatsakis: I think we should be aiming to deprecate `move` closures (and move blocks) in favor of `use` closures. Is there any reason to prefer `move`? As a nit, this language doesn't seem quite right: > For any object referenced within the closure or block that a `move` closure/block would move, `use` will `.use` that object and have the closure own the new use of the object. I think it should be, for any **place** that a closure would "move", a use closure will * move the place if this is the last use * otherwise it will `use` it Josh: Per your feedback on the RFC thread I added that as an alternative, at a minimum. Would like to discuss that tradeoff, but it seems potentially reasonable to switch to that. Josh: I've updated the RFC. ## Discarding ownership scottmcm: one thing that comes to mind here is the old "can I pass ownership to something that just wants a reference" conversation, and all the people on IRLO who seem to want to write APIs that take ownership or references. I wonder if we could say that `foo(x.use)` will automatically do the right thing for `foo(x.clone())` or `foo(&x)` the same way this RFC is making it do the right thing for `foo({x})` or `foo(x.clone())`? That would make passing `x.use` the way to write "I don't care; just work" and then R-A can suggest turning it into `&x` if that would be fine. (Intentionally not including `&mut` here, since mutating a temporary is unlikely enough to be the intent that we shouldn't allow it to happen implicitly.) ## lints nikomatsakis: I think we should consider three lints * implicit copies of values that are `Copy` but not `Use` -- lint against this to help people detect copies of `[u8; 1024]` * invoking `clone` on a value that implements `Use` -- it's basically always better to write `x.use`, we could add some alias if you **really** want to clone, or encourage people to write `Clone::clone(v)`, but I'd like to see the code where it's critical that you invoke `clone`. * use of `move` closure -- encourage people to adopt `use` closures, which are strictly more efficient Josh: All three are captured in the RFC. ## Cow tmandry: `Cow` should impl `Use`, right? Josh: The borrow case is cheap, the owned case may not be? I don't think we'd want that. tmandry: I guess you're right, but I find that kind of unsatisfying.. maybe we would have designed `Cow` differently if we had `Use` first. scottmcm: capturing cow by reference would normally be fine for those cases, no? Josh: It seems like there'd be value in some kind of ArCow, which *could* be `Use`. :) ## Drawbacks of `.use` tmandry: Generally +1 for this syntax, but I think users might infer from that `.clone()` calls user-defined code while `.use` does not from the fact that the first is a method call. I do wonder if we're going with the keyword syntax because it's an elidable call, when we might want elidable calls for clone anyway. ## More thoughts on use cases scottmcm: ~~Continuing the idea of discarding ownership above, I started wondering about whether we can have `for x in container.use` do something smart.~~ Oh, no, I guess not, because containers won't be `Use`. ## Splitting a used object into fields tmandry: If we have `foo.use.x`, we could rewrite that as `foo.x.use`. I think we would want to leave space for this in the RFC. Perhaps not just for a single field, either?