--- title: "Design meeting 2025-08-27: Ergonomic RC" tags: ["T-lang", "design-meeting", "minutes"] date: 2025-08-27 discussion: https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202025-08-27.3A.20Ergonomic.20RC/ url: https://hackmd.io/L7RV27tgTqm7Xem_xddmBQ --- # Ergonomic RC design document review This document is for review by the lang team. It covers the key learnings so far from the ergonomic RC design and proposes possible paths forward. ## What we are trying to solve Reference counted data structures are widely used throughout Rust, both with the `Rc` and `Arc` boxes but also as impl details of types like channels (both in [std](https://doc.rust-lang.org/std/sync/mpsc/fn.sync_channel.html) and runtimes like [tokio](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.channel.html)) and in with [persistent data structures](https://en.wikipedia.org/wiki/Persistent_data_structure) in libraries like [bytes](https://crates.io/crates/bytes) or [im](https://crates.io/crates/im). However, working with them is actually surprisingly fraught, both in terms of ergonomic pain and in terms of the risk of surprising runtime behavior. ### Concern A: Clone is really two operations The `Clone` trait traditionally implements a "sheep-clone". That is, a hybrid of shared and deep where *owned values* are deeply cloned and *shared values* are shallowly cloned. This means that when a user sees `some_value.clone()` in their code, it is difficult to know what kind of operation that corresponds to: * For a type like `Vec`, it can result in significant allocation and recursive cloning, which can be expensive, and which results in two *independent* values. * For a type like `Rc` or `Arc`, it is a fairly cheap operation, and results in two *linked* values, in the sense that there are now two handles to the same ref-counted value. If that value contains interior mutability, that is observable and important. This behavior is not an accident. "Sheep" clones correspond very well to what users want. However, it also makes it harder to "locally audit" Rust code and presents a refactoring hazard. When combined with type inference, it is possible for small changes in type signatures to have a large impact on performance. For example, changing a value from `x: Rc<Vec<T>>` to `x: &Vec<T>` results in massively different performance. ### Concern B: Clone and closures interact poorly Rust's closure rules interact poorly with clone-able data, often not giving the desired behavior. This concern was first raised in the context of GUI applications, but it applies more generally. One salient example comes from CloudFlare where their server had a context with a number of components: ```rust struct Cx { data1: Arc<Data1>, data2: Arc<Data2>, // ... dataN: Arc<DataN>, } ``` When spawning tasks, they needed to provide some subset of these contexts: ```rust fn spawn_stuff(cx: Cx) { std::token::spawn( async move { process1(cx.data1); } ); std::token::spawn( async move { process2(cx.data1, cx.data2); } ); // Error: cx.data1 is already moved } ``` This results in a confusing error with no obvious fix. Writing `cx.data1.clone()` in the closure will not change the capture behavior, as the clone occurs when the closure *executes*, but what is needed is a clone before the closure starts. This is a perennial source of confusion even for experienced Rust developers. Perhaps the most elegant solution is to rewrite the closure so that it uses a capture variable `cx_data1`: ```rust std::token::spawn( { let cx_data1 = cx.data1.clone(); async move { process1(cx_data1); } } ); ``` But people often do not think of this on their own. A common example is to create fresh variable names for the clones. Consider ```rust let cx_data1 = cx.data1.clone(); std::token::spawn( async move { process1(cx_data1); } ); ``` Consider for example this snippet from Amazon's [Q CLI](https://github.com/aws/amazon-q-developer-cli/blob/bd7521c9a5eb57898254132b47f7fd6dd9e6919d/crates/chat-cli/src/mcp_client/server.rs#L91C1-L105), which creates a `pending_requests` that needs to be shared amongst many closures: ```rust impl<H> Server<StdioTransport, H> where H: ServerRequestHandler, { pub fn new(mut handler: H, stdin: Stdin, stdout: Stdout) -> Result<Self, ServerError> { let pending_requests = Arc::new(Mutex::new(HashMap::<u64, JsonRpcRequest>::new())); let pending_requests_clone_one = pending_requests.clone(); // <-- AWKWARD!! let pending_request_getter = move |id: u64| -> Option<JsonRpcRequest> { match pending_requests_clone_one.lock() { Ok(mut p) => p.remove(&id), Err(_) => None, } }; ... let pending_request_clone_two = pending_requests.clone(); // <-- AWKWARD!` ... } } ``` ### Concern C: Do I really have to write clone? Concern B was Using reference-counted data in Rust allows for much easier sharing of data, but for some applications the need to write `clone` even outside of a closure feels arduous. For example, Ralf often says that the main reason that MiniRust is written in a "Rust dialect" is because he doesn't want to write `clone` everywhere. ### Concern D: Deferred reference counting The optimal way to do reference counting is generally "deferred RC". The basic idea is that so long as you know there is some "root" holding a ref on the stack, you can pass aliases to the object without incrementing the ref count. (This is how Objective C's reference counting scheme, the only sensible C-based memory management scheme in the history of the world (in Niko's opinion), worked, for example.) In Rust, you can do this by passing around `&Rc<T>` values: ```rust impl Foo { fn maybe_keep_value( &mut self, rc: &Rc<Vec<String>>, ) { if rc.iter().any(|e| should_keep(e)) { // only keep `rc`: self.data = rc.clone(); } } } ``` This has the advantage that the `&Rc<Vec<String>>` is copy and that the ref-count is only incremented if needed. However, this is a bit unfortunate, since one of Rust's goals is that the "obvious code is also the most performant". Also it requires double indirection which isn't really ideal (although whether that matters in practice is quite unclear). ## What we don't want to break The previous section focused on what is tricky about how we handle `Rc` today, but this section talks about what is *good* about it. ### Careful accounting of references is sometimes necessary APIs like `Rc::get_mut` or `Rc::make_mut`, or the bytes crate, sometimes rely on knowing precisely how many references exist to a given object. In cases like that, having some visible sign where a reference-count increment takes place is important. Rust has had great success with the principle that "notable things should be visible, if lightweight" -- having a `?` be explicit in the code has helped me identify errant control-flow, for example, and it's easy to imagine that being able to "see" ref-count increments clearly indicated in the code would help to debug objects living longer than expected and so forth. ## What we have done so far ### [RFC #3680][] [RFC #3680]: https://github.com/rust-lang/rfcs/pull/3680 Josh Triplett authored [RFC #3680][] which proposed (in short): * introduces `trait Use: Clone` which is intended to indicate values whose clone is "lightweight" (essentially, a ref-count increment; more details below) * new syntax `x.use` which: * checks that `x` implements the `Use` trait * invokes the clone operation to produce a new value `y` * well, the details are a bit more subtle: * if `x` is `Copy`, then it does a copy. Always. Note that clone may do different things. * the compiler may optimize away the clone if it sees it is not necessary, e.g., if this is the last use of `x` * new syntax `use || foo(x)` which is like `move` except: * if the captured place `p` has a type that implements `Copy`, it is copied * (i.e., desugared to `Closure { closure_field: p, ... }`) * if the captured place `p` has a type that implements `Use`, it is "used" * (i.e., desugared to `Closure { closure_field: p.use, ... }`) * else, the field is moved * (i.e., desugared to `Closure { closure_field: p, ... }`) ### Implementation Santiago has implemented this feature "more or less" as described with some variations: * the `Use` trait is called `UseCloned`, to indicate that when the value is "used" it should clone vs move. This was intended to be working towards a mental model where `a = b` was shorthand for `a = b.use` and `.use` operated in the same way as the closure capture rules described above. * This is a different mental model than what the RFC was describing, in that in this version, the "use" operation is just a thing you can do to any type, but its effects vary depending on what traits the type implements (it may copy, it may move, and it may clone). * the last-use optimization is partly implemented but it revealed some complications, described below, in particular around unsafe code. ## What we learned The RFC conversation and experiences from implementation have yielded some insights along the way. ### Feedback on the RFC I authored [this summary comment](https://github.com/rust-lang/rfcs/pull/3680#issuecomment-2625526944) that captures the main comments on the RFC. In short, everyone agreed that working with ref-counted values is a real pain point, **particularly in closures**. This was cited not just by "GUI" applications but also things like PyO3. The most hard-hitting pushback I think came around whether this RFC as going to be more confusing for users. [Diggsey I think put it well](https://github.com/rust-lang/rfcs/pull/3680#issuecomment-2301770660): > Having to navigate a codebase filled with this strange .use keyword, which is hard to explain (it copies the value.. except when it doesn't, and it can be used on Use types, but not Clone types, but Use is not Copy, it's different… When is something Use? What makes something lightweight? Uh… read this essay. Why do I need to make this closure use but this one doesn't need to be? Ahahaha, yeah this is going to take a while to explain…) is more of a blocker than clone-into-closure ever was. My main takeway was that this RFC was, in some sense, trying to "have it both ways". Both to have ergonomic ref-count cloning that is explicit (`x.use`) but then, with closures, to have an automatic capture (quick -- does `use || foo(x)` increment the ref count on `x` or not? You can't tell without knowing the type of `x`.) The result is a mushy mental model for users that is neither sufficiently explicit to help identify errant clones nor sufficiently automatic to feel like Swift. ### Looking at copy vs clone vs move holistically The Rust language + stdlib effectively categorizes types into three categories today: * moveable -- everything can be moved * cloneable -- some things can be cloned * copyable -- some things that are cloneable can also be *copied*, which means that `let a = b` will implicitly move the value. * There is a strong assumption (but not a requirement) that `let a = b.clone()` would be equivalent. In fact, it can diverge in behavior and be arbitrarily more expensive, but that's the idea. However in reality there are actually five categories, though one of them * *moveable* -- everything can be moved * *cloneable* -- some things can be cloned * *lightweight cloneable* -- things like Rc and Arc, we'll define this more precisely below * *automatically cloneable* -- lightweight cloneable things where seeing the clone is simply a distraction from the purpose of the code. **This is a subjective category and will depend on the human and the goal they are trying to achieve.** * *copyable* -- some things that are cloneable can also be *copied*, which means that `let a = b` will implicitly move the value. The old maxim says that you should make things "as simple as possible, but no simpler". I would argue that the current Rust system is *too* simple, and fails to capture essential/inherent complexity. That is, grouping both "cloneable" and "lightweight cloneable" into one category actively harms code readability -- this is backed up by independent assertion by Rust experts like Carl Lerche (who frequently complains to me about this). This says to me that we absolutely *should* have some way to distinguish lightweight clones and to make them ergonomically less painful. The question then becomes whether distinguishing *automatically cloneable* from *lightweight cloneable* is worthwhile -- and whether in fact any types exist that are truly automatically cloneable *all of the time*. The existence of APIs like `Rc::get_mut` as well as libraries like bytes suggest that the existence of a reference *can* be significant, even in high-level code. ### What makes a clone "lightweight"? One other piece of feedback from the RFC is that cheap is in the eye of the beholder and what constitutes a significant execution cost will depend on the application. I think the most important variant of this is that the cost of, e.g., having `Rc` or `Arc` stick around is not necessarily just the cost of the reference but the cost of the memory that is being retained. In the case of APIs like `Rc::get_mut` it can even be a correctness concern. Nonetheless, there is a common sense notion of what makes a clone "lightweight". I believe useful criteria are something like this * *cost* -- the clone should be O(1) and not copy more than a "few machine words" of data around * *side-effect free* -- `std::mem::drop(a.clone())` should not be observable. In other words, whatever "side-effects" `a.clone()` may have (e.g., incrementing a ref count), `Drop` must undo -- and it can't do anything else! * *symmetric* -- `let b = a.clone()` and `let mut b = a.clone(); swap(&mut a, &mut b)` should not be observably different. In other words, it doesn't matter whether I * clone `a` and then use the original and give away the clone; or, * clone `a` and then use the clone and give away the original. ### Optimization and interaction with borrow checker and unsafe code [RFC #3680][] left room for a number of optimizations with the hope of addressing deferred RC in some cases, e.g. by having a "sufficiently smart compiler" that could optimize a case like this: ```rust fn print_len(data: Rc<Vec<u8>>) { println!("{}", data.len()); } let rc = Rc::new(vec![]); print_len(rc.use); // doesn't *really* need `use` drop(rc); ``` ...but that was pretty fancy. In practice we just looked at 'last-use' optimization, trying to convert `r.use` to just `r` if there were no further uses of `r`. Last use is much more feasible but it does require interaction with the borrow checker to account for indirect references: ```rust let r = Rc::new(vec![]); let p = &r; let c = r.use; // `r` is never used again! println("{}", p.len()); // but `p` is! ``` A naive analysis would convert `r.use` above to `r` which would introduce borrow check failures. Santiago implemented modifications to the borrow checker to account for this, but it's fairly complex. Still, feasible, but then you have to consider the case of `unsafe` code: ```rust let r = Rc::new(vec![]); let p: *const Vec<i32> = addr_of!(r); let c = r.use; // `r` is never used again! println("{}", unsafe { (*p).len() }); ``` This unsafe block would be invalid because the `unsafe` should just never assume that `r.use` will clone unless there is some use of `r` or a reference derived from `r` that is visible to the compiler. Overall, the lessons here are mixed. Optimization is feasible but this is more sophisticated than what Rust has done in the past. Part of Rust's philosophy has been *not* relying on a "sufficiently smart compiler" that can do sophisticated optimizations but instead just assuming a combination of "sensible inlining"/copy-propagation/const-propagation. These optimizations seem quite a bit more sophisticated and, in particular, require alias analysis. ## Alternative proposals I will sketch out the space of alternatives and their pros/cons. ### Examples For each option: * I'll show a TL;DR that tries to explain the rules. * I'll then walk through two examples: * The "Cloudflare Clone" example, where there is a context with several Rc values and closures that pick a few of them. * The "GetMut" example, where users are invoking `get_mut` ### The OG design -- what was described in [RFC #3680][] TL;DR. Introduce `Use` trait and `.use` suffix. Add `use ||` closures which will copy, move, or use depending on the type of the captured values. ```rust // Introduce an `AutomaticClone` trait // "in between" `Copy` and `Clone`. trait Copy: Use {} trait Use: Clone {} impl<T: Copy> Use for T {} trait Clone {} // by-value moves like this automatically clone let rc1 = Rc::new(vec![]); let rc2 = rc1.use; // automatically clones let rc3 = rc1; // moves // by-value closure capture (in this case, forced by `move ||`), clones let cl = use || println!("{}", rc3.len()); // clones `rc3` let cl = move || println!("{}", rc3.len()); // takes ownership of `rc3` ``` This makes the "Cloudflare Clone" example by implemented with `async use`: ```rust struct MasterContext { timer_context: Arc<TimerContext>, other_context: Arc<OtherContext>, } impl MasterContext { fn spawn_tasks(&self) { tokio::spawn(async use { do_something(self.timer_context) }); tokio::spawn(async use { do_something(self.timer_context, self.other_context) }); } ``` It will require an explicit `rc1.use` in this example: ```rust let mut rc1 = Rc::new(vec![]); let v = print_values(rc1.use); // clones, but not obviously Rc::get_mut(&mut rc1).unwrap(); // wait, we expected just one ref... fn print_values(v: Rc<Vec<u32>>) -> Rc<Vec<u32>> { v } ``` ...but it's worth noting that a similar example with closures could be hard to see: ```rust let mut rc1 = Rc::new(vec![]); let v = use || { ... print_values(rc1); // <-- Will people see this? ... }; Rc::get_mut(&mut rc1).unwrap(); ``` ### "Automatic Clone is the new Copy" TL;DR. Allow types to implement `AutomaticClone` and change "by value" usage so that it clones instead of moving, unless this is a last use or clearly unnecessary. Use lints to avoid bugs. ```rust // Introduce an `AutomaticClone` trait // "in between" `Copy` and `Clone`. trait Copy: AutomaticClone {} trait AutomaticClone: Clone {} impl<T: Copy> AutomaticClone for T {} trait Clone {} // by-value moves like this automatically clone let rc1 = Rc::new(vec![]); let rc2 = rc1; // automatically clones let rc3 = rc1; // <-- last use of rc1, don't bother to clone // by-value argument passing, also clones drop(rc2); // clones! drop(rc2); // last use of rc2, drops // by-value closure capture (in this case, forced by `move ||`), clones let cl = move || println!("{}", rc3.len()); // clones `rc3` let cl = move || println!("{}", rc3.len()); // last use, takes ownership of `rc3` ``` This makes the "Cloudflare Clone" example quite nice: ```rust struct MasterContext { timer_context: Arc<TimerContext>, other_context: Arc<OtherContext>, } impl MasterContext { fn spawn_tasks(&self) { tokio::spawn(async move { do_something(self.timer_context) }); tokio::spawn(async move { do_something(self.timer_context, self.other_context) }); } ``` But on its own, it can hide increments that may cause bugs or lead to surprises: ```rust let mut rc1 = Rc::new(vec![]); let v = print_values(rc1); // clones, but not obviously Rc::get_mut(&mut rc1).unwrap(); // wait, we expected just one ref... fn print_values(v: Rc<Vec<u32>>) -> Rc<Vec<u32>> { v } ``` We could mitigate this by having an allow-by-default lint `automatic_clone` that fires whenever an automatic clone is introduced: ```rust #![warn(automatic_clone)] let rc1 = Rc::new(vec![]); let rc2 = rc1; // <-- WARNING issued here let rc3 = rc1; // <-- not here, last use ``` However, users would have to *know* that they should use this lint. The proposed fix for this was to have an annotation on methods like `get_mut` like "should_disable_automatic_clones". This would issue a warning if `get_mut` is called in a context where `automatic_clones` is set to allow. Kind of weird, but as a result: ```rust let mut rc1 = Rc::new(vec![]); let v = print_values(rc1); Rc::get_mut(&mut rc1).unwrap(); // <-- WARNING: you should disable automatic clones fn print_values(v: Rc<Vec<u32>>) -> Rc<Vec<u32>> {...} ``` **Analysis.** This design arguably *oversimplifies*: * By default, it equates "lightweight clones" and "automatic clones". * Users can opt out of this in a crude way by warning on the lint, but then it is equating "clones" and "lightweight clones". * In other words, when you see `foo.clone()`, you don't know if that is a lightweight clone or not unless you know the type of `foo`. * Moreover, the "lint if this lint is not on" mechanism is clever but perhaps a bit *too* clever. ### Use-side annotation > On the RFC thread, [Dario proposed an alternative take](https://github.com/rust-lang/rfcs/pull/3680#issuecomment-2308983091). The idea was not to have the *type author* decide what clones they want to see but to have *library consumers*. The following is adapted from Dario's comment. It is intended to mark certain types as being "cheap to clone", to avoid .use doing expensive clones unnoticed. Since it's a trait, it's the library defining the type who chooses to mark a clone as "cheap" or not. However, in my opinion, whether something is "cheap" or not is \[objective\], and the criteria changes depending on the context of the user's code. In this proposal, we don't add any trait. Cheap clones are still `Clone`. Instead, we add a way for the user to specify "I consider cloning X cheap within this code" , for example with an attribute: - `#[autoclone(Rc)]` if you consider Rc clone cheap but not Arc. - `#[autoclone(Rc, Arc)]` if you consider both cheap. - `#[autoclone(*)]` if you want to quickly write experimental non-perf-critical code without the borrow checker bothering you. CloudFlare would add a "autoclone" annotation, likely at the crate level: ```rust #![autoclone(Arc)] // turn on autoclone for the whole crate. struct MasterContext { timer_context: Arc<TimerContext>, other_context: Arc<OtherContext>, } impl MasterContext { fn spawn_tasks(&self) { tokio::spawn(async move { do_something(self.timer_context) }); tokio::spawn(async move { do_something(self.timer_context, self.other_context) }); } ``` The `get_mut` example is a bit more subtle. On its own, of course, this code would not compile: ```rust let mut rc1 = Rc::new(vec![]); let v = print_values(rc1); // clones, but not obviously Rc::get_mut(&mut rc1).unwrap(); // wait, we expected just one ref... fn print_values(v: Rc<Vec<u32>>) -> Rc<Vec<u32>> { v } ``` but it could be embedded in a crate (like the CloudFlare example) that enables autoclone globally, in which case it would fail. This also comes *close* to crossing a bridge we've traditionally avoided, where code changes meaning depending on where it is copy-and-paste (although you can view this as the code always having the same meaning -- insert a clone -- but with errors occurring if the autoclone is not enabled, but then you have to ensure you will do reasonable optimizations.) Advantages: - Places the decision of what's "cheap" on the end-user, not on the library author. The end-user is the best positioned to choose, because what's "cheap" on one context might not be in another. - No new syntax needed, clones are fully implicit but only if the user does opt-in with `#[autoclone]`. - Avoids the complexity of adding a third trait `Use` in addition to `Copy` and `Clone`. - Available instantly for all libraries, without them having to release updates to implement `Use`. **Analysis.** TBD ### "Use, use everywhere" TL;DR. Introduce a `Use` marker trait for lightweight clones and `.use` syntax to perform a lightweight clone. Change closure capture so that if values are "used" inside the closure we will capture "by-use" as well. ```rust // Introduce an `AutomaticClone` trait // "in between" `Copy` and `Clone`. trait Copy: Use {} trait Use: Clone {} impl<T: Copy> Use for T {} trait Clone {} // The syntax `x.use` would // (a) require that the type of `x` implements `Use` and then // (b) copy if the type of `x` is Copy or // (c) be equivalent to `Clone::clone(&x)` otherwise. let rc = Rc::new(vec![]); let rc1 = rc.use; // increments ref-count let rc2 = rc; // moves // If a variable is "used" within the closure, // then we bump it up to "capture by use". // // This means that the closure "uses" the value when it is created. let cl1 = move || println!("{}", rc3.use.len()); // clones `rc3` let cl2 = || println!("{}", rc3.use.len()); // *also* clones `rc3` ``` The "Cloudflare Clone" example would require `.use` in various places: ```rust struct MasterContext { timer_context: Arc<TimerContext>, other_context: Arc<OtherContext>, } impl MasterContext { fn spawn_tasks(&self) { tokio::spawn(async move { do_something(self.timer_context.use) }); tokio::spawn(async move { do_something(self.timer_context.use, self.other_context.use) }); } } ``` The `get_mut` example now has an explicit `.use`: ```rust let mut rc1 = Rc::new(vec![]); let v = print_values(rc1.use); Rc::get_mut(&mut rc1).unwrap(); fn print_values(v: Rc<Vec<u32>>) -> Rc<Vec<u32>> {...} ``` **Analysis.** This design preserves the distinction between "lightweight clones" and "clones". Users can look for `.use` and they know the type in question is `Use` and not just `Clone`. There is an explicit signal whenever arbitrary code and/or reference count increments occur. The design does imply some ergonomic hit for automatic clones, but it introduces an appealing consistency between closures and regular code. In other words, if I have code that does: ```rust let b = a; do_something(&a); // <-- Error: `a` moved ``` the fix is to introduce `a.use`: ```rust let b = a.use; do_something(&a); // <-- OK! ``` Now if that use occurs in a closure/future: ```rust let b = async move { a }; do_something(&a); // <-- Error: `a` moved ``` The fix is the same, change to `a.use`: ```rust let b = async move { a.use }; do_something(&a); // <-- OK ``` The explicit `use` also permits a lint for optimizing by removing `.use`, for example: ```rust let b = a.use; let c = a.use; // <-- WARNING: as `a` is not used again, this could be converted to `let c = a` ``` However, there is one concern, which is that the generated code does sometimes perform additional increments that aren't strictly required. For example: ```rust let r = Arc::new(vec![]); let c = || send(r.clone()); // captures `r` by ref, clones on each invocation // vs let r = Arc::new(vec![]); let c = || send(r.use); // clones `r` on closure creation, clones again on each invocation ``` The workaround would be to either invoke `clone()` (not great, no longer distinguishes lightweight clone) or introduce some intermediate variables: ```rust let r = Arc::new(vec![]); let c = || { let r = &r; // force by-ref capture send(r.use); // clones `r` on closure creation, clones again on each invocation }; ``` ## Questions for discussion ### Overall preference My preference is for "use, use everywhere". I find it to be an elegant overall solution to the problem that follows in Rust's tradition of recognizing important distinctions but making it possible to manage them in a lightweight syntactic fashion. I'll dive in a bit more in the follow-up questions to nuances. To me, the mental model for user's works: * `let a = b` does a memcpy at runtime, moves or copies * `let a = b.use` indicates a "lightweight clone" * `let a = b.clone()` is a heavyweight clone One interesting bit is that `let x = move || b.clone()` will not capture a "clone" but rather moves `b`. That makes sense to me because the precise number of heavyweight clones ought to be very clear, but it could be a point of confusion. ### Bikeshed The name "use" is pretty overloaded. Alternatives suggested: * `Retain` trait and `x.retain()`, based on Obj. C * `Claim` trait and `x.claim()`, based on "Niko though of it" * ... One thing to note is that, in the "Use, use everywhere" design, `x.use` is definitely "recognized" by the compiler in important ways (it has codegen specialized on copy, it affects closure capture), so if we make it a regular *method*, that's a bit surprising to me. Not a blocker, and we could add a new keyword too. ### Interaction with capture of copy values The proposal here is that `|| process(x.use)` will clone `x` when the closure is created. This introduces some asymmetry with the behavior for copy: ```rust let mut i = 3; let c = || process(i); // captured `i` by reference i += 1; // <-- ERROR c(); ``` Interestingly, users could override this explicitly: ```rust let mut i = 3; let c = || process(i.use); // <-- capture by "use" i += 1; // <-- OK c(); ``` However, given that `Copy: Use`, I might expect `|| process(i)` to capture a copy of `i` and not by reference. However, that would admit potential bugs (e.g., did you expect to capture a copy of `i`?). We previous decided (some time back...) to capture by-reference in this case because it left less room for bugs. The model for closure capture is that we track how each place is used. Today there is kind of a lattice where we look at how the variables are used and take the max of: * capture 'by-ref' * in non-move closures, used for "by value" use of a copy type * capture 'by-mut' * capture 'by-value' Another interesting example: ```rust let mut i = 3; let c = || { let j = i; // Today, because `i32: Copy`, this is a by-ref use i += 1; // Bumps up to `by-mut-ref` }; i += 1; // <-- Error, two by-mut-refs c(); ``` Upon consideration I think I like the idea that we still consider "by value" usage of `Copy` types to be by-reference but we consider `.use` to be "by-use": * capture 'by-ref' * in non-move closures, used for "by value" use of a copy type * capture 'by-mut' * capture 'by-use' * when there is an explicit `place.use` * capture 'by-value' which gives users the ability to specify either one. ### Autoderef Today, given `x: &Rc<T>`, `x.clone()` will compile to `Clone::clone(x)`, and hence the result is of type `Rc<T>`. What should `x.use` do? Probably the same? ### Explicit capture clauses We've gotten pretty far with our automatic capture closure inference, and the "use, use everywhere" proposal pushes it a bit further, but we should consider adding some form of explicit capture clauses ([previous proposal](https://hackmd.io/@nikomatsakis/SyI0eMFXO?type=view)). For example: ```rust let c = || test(r.use); ``` could be compiled to ```rust let c = move(r.use) || test(r.use) ``` or ```rust let c = move(r = r.use) || test(r.use) ``` under this design. This would also allow explicitly distinguishing by-ref capture, which might be desirable: ```rust let c = move(&r) || test(r.use) ``` ### "Last use" and other optimizations One way to extend the previous design would be to give the compiler room to perform "last use" optimization, as discussed previously. The idea is that `r.use` *might* opt not to clone depending on how `r` (or references to `r`) are used looking forward. As noted earlier, this requires borrow check integration, and raises the possibility of interactions with unsafe code. This would help us to optimize cases like this: ```rust let r = Arc::new(vec![]); let c1 = move || send(r.use); let c2 = move || send(r.use); ``` Today this would result in both closures "use"'ing `r`, but with last-use optimization it could be: ```rust let r = Arc::new(vec![]); let c1 = move(r.use) || send(r.use); let c2 = move(r) || send(r.use); // - take ownership here ``` However, the more concerning case is when we capture by-use instead of by-reference: ```rust let r = Rc::new(vec![]); foo.do_something() .unwrap_or_else(|| send(r.use)); // -- would "clone" `r` ``` If this code is inlined, we might be able to eliminate this clone, but in general it requires knowing whether the closure needs to be `'static` or not. We can optimize in various heuristic cases but it's tricky: ```rust let r = Rc::new(vec![]); must_be_static(|| send(r.use)); // <-- this cannot be by-ref capture fn must_be_static<T: 'static>(t: T) {} ``` --- # Discussion ## Attendance - People: TC, Tomas Sedovic, Taylor (cramertj), Tyler Mandry, Santiago, Aapo Alasuutari, RustyYato, simulacrum, Josh, Niko, Yosh ## Meeting roles - Driver: TC - Minutes: Tomas Sedovic ## Context & background Niko: I don't have a lot to say that wasn't in the doc. I became fairly convinced was a major pain ponint in Rust. I want to fix it but it's tricky. Tried to include the many lessons and different tradeoffs. I want to put this feature on path to stable. Accept the RFC, have this be a feature that's coming. So for us to align on the big picture. The big blocker right now is what is the high level model (use use everywhere, automatic clone is a new copy, something else entirely)? ## Vibe check ### TC (I read the document early, so I have some extended vibes.) TC: Wonder whether there's a number of orthogonal things and I wonder whether we can slice them out. I start seeing these reference-counted pointer types (Rc, Arc) -- they're runtime borrow-checker essentially. Us using them, cloning them is a kind of runtime reborrow. Is that the right way to see it. Is there a way to compare them. Then started looking at the behavior of closures and closure captures. What's the right way to slice through all the different problems. TC: I do start wondering about use use everywhere whether it solves enough. TC: Is the closure capture really related to .use behavior? I'm sympathetic to the points Dario raises. This feels more like a use-site decision rather than dev-site decision. TC: I wan to highlight the deferred RFC which is the right thing here. #### Praise and applause Before I get into the details, let me first say I think the document is clear-headed and insightful, and I got value from reading and thinking about it. #### The deferred RC pattern As the document points out, the optimal way to do RC is the deferred RC pattern. It'd be nice if we could push people toward this kind of success, or at least not push them away from it. It seems, however, that the "use everywhere" design does somewhat push people away from this, which would be unfortunate. #### Closure behavior is a bit odd As discussed in the document, the behavior as it relates to closures is a bit nuanced and maybe a bit odd in general. Need to think more about this, but it does stand out to me as a potential flaw of "use everywhere". #### Does it solve the MiniRust problem? The document notes in passing that: > For example, Ralf often says that the main reason that MiniRust is written in a "Rust dialect" is because he doesn't want to write `clone` everywhere. This isn't the only place that this sort of thing has happened. It's not immediately clear to me that replacing `.clone()` with `.use` plus the magic closure capturing behavior does enough to resolve this. #### Still doesn't let downstreams solve their own problems By requiring a new trait and tying that trait to this feature, this still doesn't let downstreams "solve their own problems", and it still requires upstreams to make maybe-squishy decisions about when downstreams are allowed to use this feature. As I [wrote](https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/.60ergonomic_clones.60.20without.20trait.3F/near/506407329) earlier: > There's a probably a design axiom here that I hold. I'm trying to put words to it. I think it's along the lines of, "an upstream author must have a 'good reason' for restricting what a downstream can do." > > That is, if you're providing a type, and it doesn't do a thing, or just you haven't implemented it to do that thing yet, or you're holding space for it to maybe later do or not do that thing... those are all good reasons. > > But I don't think you get to say, as an upstream, "you can't use my type in a macro." Even if, as an upstream, I've received a lot of silly bug reports because people were using my type with macros in ways they shouldn't have been, I can't express my opinion on that by forbidding downstream authors from using macros with my crate's types. Nor can I say that a downstream can't use lifetime elision with my types, or can't implement traits for them. That is, I don't just get to restrict what features or conveniences of Rust a downstream can use. > > Here, we're essentially adding an autocloning features (the `use || ..` in particular), but then `UseCloned` gives the upstream control of whether you can use that convenience. It just doesn't feel right to me. The type either supports `Clone` or it doesn't. Whether the downstream wants to use the convenience just doesn't seem like the upstream's call to make. > > If we want to give the upstream a way to signal "this might allocate" or "this might copy something really big" or "this might do I/O" or other such things -- even just "I personally think this is expensive (or not)" -- and then let downstreams make an informed decision, then that's another matter. At root, I'm still just not sure that upstreams need to maintain a firm grip on how downstreams are able to use whatever language features we add, now or later, related to this. #### Alternative: Attribute on types Based on what I wrote above, Oli [suggested](https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/.60ergonomic_clones.60.20without.20trait.3F/near/506412978) the following: > Tho now that I think of it, it is actually more like `must_use`. So maybe there should just be an attr on types that makes `use` not emit a lint if used on the type. Then everyone has control. Something like this still seems rather plausible to me, and I'd put this forward for consideration. #### Alternative: Trait, but not hard required Alternatively, we could say that it does make sense to use a trait to designate that the author is promising that the corresponding `Clone` impl meets (and will always meet) certain definitions of lightweightness. We could have that trait, and we could tie it to linting, but we could still allow `.use` when the trait isn't implemented, maybe with a lint. It seems as though some of the features that we'd be adding here, e.g. automatically eliding the last clone, would be just as useful for other kinds of clones, and that people would reasonably ask why this mechanism couldn't be used for those too. #### Many copies are more expensive than many clones Due to the fact that arrays of any size are `Copy`, there exists a significant existing "inversion" in the `Copy`/`Clone` space -- there are a significant number of values that cost more to copy than a significant number of other values cost to clone. We of course do these expensive copies today without any ceremony. #### `get_mut` and reborrowing Thinking about the `get_mut` case, and that `Rc` is essentially runtime borrow checker implementation, it strikes me that the uses we'd consider for autocloning are a kind of auto reborrowing. E.g., this code ```rust let mut rc = Rc::new(vec![]); let v = get_values(rc); //~ Auto clone. let v_mut = Rc::get_mut(&mut rc).unwrap(); _ = *v; fn get_values(v: Rc<Vec<u32>>) -> Rc<Vec<u32>> { v } ``` probably doesn't surprise me much more in its runtime failure than this code ```rust let mut r = &mut vec![]; let v = get_values(r); //~ Auto reborrow. let v_mut = &mut *r; _ = *v; fn get_values(v: &Vec<u32>) -> &Vec<u32> { v } ``` does in its compile time failure. #### Deref Doing automatic cloning wouldn't be the first instance of us running user-specified code without a lot of syntax to indicate it. E.g.: ```rust use core::ops::Deref; struct W<T>(T); impl<T> Deref for W<T> { type Target = T; fn deref(&self) -> &Self::Target { println!("Side effect..."); &self.0 } } fn main() { let x = W(0u8); let _y = x.pow(2); } ``` This doesn't seem to cause a lot of problems in practice. #### Having crossed the bridge Due to the mentioned inversion -- that many copies are more expensive than many clones, the fact that we already run user-specified code in `deref` without a lot of ceremony, and, in particular, the striking analogy between reborrowing and autocloning of `Rc`s, I just feel like we've somewhat already crossed the bridge here. #### Intersection with `Reborrow` trait work? If we do see the autoclone of an `Rc` as a kind of autoreborrow, is there any degree to which the ongoing experimental work on a `Reborrow` trait might overlap with or possibly supersede the work here? Maybe that's something to look into. ### tmandry I think "use, use everywhere" is a nice starting point. Personally I am enthusiastic about going farther, but it's not clear to me from this doc whether we have a clear path forward. I'm aligned with the idea that we should empower users to make this decision for themselves. I suspect a large majority of users do not want or need explicit ref counts. In a lot of cases where explicitness is needed, we either have code that tells us it is needed (e.g. `Rc::get_mut`) or the user has specific enough needs that they are using custom ref counting types anyway, so I wonder if we can make use of that. ### scottmcm (Not present.) ### joshtriplett Like Niko, my preference is for "use, use everywhere". Despite writing the previous RFC, I like the ergonomics of "use within closure" and making closure capture smarter better than the `use ||` proposal. Also, leaving aside the benefits for closures and async blocks, I am *eager* to use `.use` to replace clone everywhere it currently can. I think it's helpful for self-documenting code, to specify expectations, so that any remaining `.clone()` always identifies a heavyweight clone. My main open question with moving to the "use, use everywhere" model is the likelihood of many cases that will make unnecessary references when they could have handed over ownership. That isn't the end of the world, but it would be nice to avoid, ideally without having to write something like `move(x.use) ||`. I don't know exactly how we could best avoid this, and I don't want to lean too hard on "sufficiently smart compiler". I'd be pretty happy letting those doing the lang experiment try some things and report back on what works, with the guidance that that *does* seem important. Apart from that, I think there's a minor potential issue that a `.use` inside a closure might be easier to go unnoticed rather than something at the *beginning* of the closure. However, that's not wildly different than with `use ||`; while that flagged that *something* would get use-cloned up front, it didn't say *what*, and having the `.use` attached to the specific values is *more* explicit. I also think we could address this in multiple ways; for instance, this *is* the kind of thing an opinionated allow-by-default lint could help with, that requires naming things in an explicit capture clause for anything that gets moved *or* use-cloned (but not necessarily things that are just referenced). Does the use of `x.use` inside a closure get in any way affected by `||` versus `move ||`? I wouldn't expect it to. Regarding the idea of users doing this downstream: I'm sympathetic, but code doesn't exist in a vacuum. I think there's huge value in common standards here, where it works for Arc/Rc/etc, and doesn't work for Vec. On a different note, I'd be interested to use the programming language where "Automatic Clone is the new Copy". I don't think that language should be Rust, but it's a language that would seamlessly interoperate with Rust, and would be really nice for certain kinds of high-level programming. Might go well with the idea of pervasively using `ArcRef` and field projection for everything, too. I think the result would be effectively aiming for "anything Python can do, but faster" in the same way that Rust nicely targets "anything C can do, but safer". I would very much like to see a concrete proposal for "Explicit capture clauses". It seems like that *should* be a pretty straightforward proposal, though it may have some ergonomic wins that might be interesting to specify (e.g. allowing `(x.clone())` rather than `(x = x.clone())`), but it still seems straightforward. Is anyone currently working on that? Are there concrete plans for an RFC? ### Nadri Passing by, can't attend fully but I read the doc. Great doc btw! "Giving downstream users control" feels like a red herring to me, my feeling is that the things we'll want to implicitly clone are almost always smart pointers, and for these letting upstream choose is the correct move. I don't buy that getting `Arc::get_mut` to work is hard in this world. It's doable with scopes, and yeah we can have a lint for the tiny minority of users who need this. I'm getting a sense that Rust is moving into a phase of making smart pointers a lot more powerful (with arbitrary_self_types, deref patterns, field projections, reborrowing, pin ergonomics, ..). In that context, having a simple `AutoClone` trait would fit well imo. It sounds like I'm arguing for "lightweight clone = autoclone", I think I'm actually trying to argue for "autoclone should be reserved for container/pointer kind of things, kinda like `Deref`". Well, rather than "it should be reserved", what I'm hypothesizing is "if we encourage users in this way, the resulting code/APIs will be nice". Deferred RC without double indirection would fit very well with this vision. I don't know how to achieve that though. Regarding explicit `.use`, imo it does not remotely pull its weight, and it gives me a strong ick x) ### Santiago About "Use, use everywhere" proposal: - Learnability of `.use` in : Main worry around people learning Rust facing yet another "complexity". Would prefer just a somehow "specialized" method call. - If we stick a `.use ` everywhere it won't be ergonomic in terms of repetition "calls" of `.use`. - In this specific proposal, I think was better to stick with `use || {}` for closures, Dioxus people in their original proposal they care mainly about this use case and avoiding the repetition of calling clone. We may even repurpose capture rules on `|| {}` as in some of Niko's old proposals that aren't mentioned in this document. Maybe this could be addressed by the "Explicit capture clauses" if we were going to allow `move(x.use) || do_something(x)` and x inside the closure is lightweight cloned. ### Aapo The implemented "one use, many possible usage flavours" interacts with Reborrow lang experiment. The list of possible flavours would expand to 4 `UseCloned`, `UseCopied`, `UseMoved`, and (new) `UseReborrowed`. The "movable, copiable, clonable" split also overlooks immovable / pinned types, and reborrowable types. That's may not be a pertinent point to the proposed extension of the model, though. ### simulacrum .use is good. I'm not sure it goes quite far enough to solve everything, but it's a good step in the right direction. ## Vibe check responses nikomatsakis: I want to response to some of what I see in the vibe checks. Consider this my "vibe check" on the "vibe checks". - Nadri's point that this largely corresponds with Deref and "smart pointers" resonates with me, but feels incomplete. I think mpsc producers and Bytes are examples where I would want this pretty badly, and in particular they are *very commonly* a type that you want to share amongst closures. - Santiago's point about learnability and whether "Use use everywhere" addresses it, my feeling is actually that it does. I think there's an inherent complexity here of learning about how cloning a ref works etc, and `.use` lets you make that distinction clearer. In other words, it's true that there's more to learn, but it's also true that you need to learn that *anyway*. - The more interesting point to me is that I think this model really *really* works well for closures. When you get a "this ref-counting thing `r` may have been moved" error, the answer is *always* to do `r.use`, regardless of where it appears. I think that's a good thing. The "Automatic Clone is the new Copy" proposal also manages it. - All that said, it's clear that Use, Use Everywhere is "more complex" (more language surface area) than the Automatic Clone is the new Copy proposal. But I guess the point is that it feels like the kind of place where does tend to "go deep", reflecting real distinctions that can matter -- perhaps the question is *just how often do they matter*. - To TC's various points: - Regarding arrays, I think we should make `[T; N]` not copy actually, regardless of `T`, or probably a lint given backwards compatibility. Instead I'd want to introduce a `Memcpy` trait and have it implement that (where `Copy: Memcpy`). I'd prefer for you to derive copy on a newtype'd struct if that's what you want. I don't think there's any good way out otherwise, it's too easy for arrays to become "big ol' footguns". That said, structs can be big too, so I think this merits separate distinction. - Regarding the comparison to dynamic behavior, I think the difference between a *static* error and a *dynamic* error is huge. In other words, the fact that you get a borrow check in one scenario can be confusing, but never leads to *surprise*. - Asking whether this "does enough"-- - perhaps not for minirust, but that's a high bar, it's mathematical notation - but the GUI case is interesting - Perhaps what this comes down to: - I am convinced by Josh Triplett's position that Rust's core value prop is "less surprise". I'm worried that, if we're not careful, we hit the wrong balance here. - On Dario's "use-side" example: - My concern here is that it moves us away from "No Surprise". Taking some notes during others vibes checks: Key questions: - *Do upstreams need to control this?* - In favor of: * - Against: - *Should we go farther?* How to think about this, AutomaticCloneIsTheNewCopy vs UseUseEverywhere? * - *Is UseUseEverywhere ergonomic enough to carry its weight? How easy will it be to learn?* - In favor of: - It does have you write `.use`, but you write it consistently everywhere, and there's a real distinction here. It carries its weight by analogy to `?`. - Against: - Consider a GUI library. Will this cut it? - Next steps: - Could we talk to Will Crichton or others and try to evaluate this empirically? - Would nightly experience help us? - *What about "last-use" optimization and the rest?* Potential tenets that are involved: - *Efficient by default.* "you do the most efficient thing" - *As explicit as you want to be.* You should be able to be explicit when you want to. It helps with teaching to be able to write things out and helps people gain a solid mental model when there's syntax they can *see* (or: *read*). - Nadri: in all these proposals we can go back to explicit `clone()`s, so that's easy no? - niko: not for closures -- or at least it's *tricky*, but yes. - *Highlight what's important (or No surprises).* - *Context-free code.* You should be able to read Rust code and know what it will mean with minimal surrounding context. ## Use use everywhere nikomatsakis: what really convinced me was that `.use` felt like `?`, I could imagine sitting there, scratching my head at why this `get_mut` fails before realizing *oh---there's an automatic clone* or *how is there a cycle here, why is there a memory leak?* If there's any point in Rust, it's to never have those moments. nikomatsakis: I do think there's a path where we start with `.use` and potentially add another layer later, making it not required, but I'm not sure if we would need to. Josh: I don't know what fraction of Rust users are the peole who need that level of explicitness (.use everywhere) but the people who need it are becoming Rust users. So the question is: do people who need that level of control get that level here. Tyler: One reason I like .use is it represents a good step forward. I'm interested in exploring going forward. But I do find some of the examples you gave Niko compelling. Debugging an Arc cycle is difficult -- something I've done in Swift. My inclination currently is to ship something with .use and then explore after that. TC: I like that this conversation is eliciting the values of what Rust should be. On the point of "people should just use Go or Swift" -- I want to capture those use cases. One of the outcomes of the Rust experiment is that we do capture usecases where people would otherwise use Python. I don't want the language to be too fussy. I want to justify those examples. For the things like the question mark theree are great reasons for people to write it. I'm not sure that's true here too. The subset of users most okay with autoclone everywhere -- they're operating at the lowest level: people like Dario, Rust for Linux. We have crossed this bridge already -- when you look at autoderef behaviour, that's running arbitrary code with little syntax. I struggle to see the difference. I'm sure it's not as compelling as the question mark. I'm open to having lints highlighting this, but I see a use-case with Rust for people who write a lot of Python code. Josh: I want to be explicit that I was trying to be descriptive not prescriptive regarding people who can use Python or Go. I want to capture those people too, and make Rust work for them. I'm not suggesting we cede those users to other languages. I'm just saying that today de facto people who don't care about references have a lot of alternatives everywhere. But people who *do* care don't have a lot of options and Rust is the best one that exists. I want to capture the broader set of users, but I don't want to lose the people who *need* Rust. Niko: I'm a little wary of the language of "capturing". I want Rust to focus on the areas where it's best. And part of doing a great job for writing foundational software is being good enough at all levels -- up to the shell script. Niko: I appreciated you pointing out the thing about values, TC. Often I'm happiest with my decision when I can state the hypotheses that would help me point the outcome and then see which ones are true. Niko: One hypothesis is: `.use` will be good enough. I know it's not what everyone wants, but it's a significant step up for `.clone()` especially in closures but also the compiler could tell you. We could test this, partly by getting it on nightly, asking folks etc. Niko: Another hypothesis is: I do believe there's a constituency that wants to distinguish lightweight/non-lightweight clones but wants to not see them at all. *Niko notes* (but didn't say out loud): This implies to me that we would *eventually* want `use` or at least `.retain()`, but in particular `.retain()` doesn't work with closures unless we want to make it work. That is an alternative: we could allow traits to be annotated with some special annotation such that the language understands them when used in closures. *Niko notes:* when it comes to capture semantics, I feel more comfortable with the idea of the compiler optimizing closure capture to capture a reference that will be used *when it can*. Josh: I think the phrasing you were using makes sense talking about the next steps. For two of the hypothesis it's something we don't have to distinguish them right away. Maybe this will be good enough; maybe we'll want something further. For either of those hypotheses, we will need this syntax, in the second case so that we have something that could translate into. So the only case this isn't the right next step is if we want to do something where this isn't the right model. I'd argue this is the right next step to try. Before we go stabilize it we should get some feedback on ways to eliminate unnecessary references or finding we need to use a different model. TC: I agree with that. The thing I most want to distinguish is between the use site syntax and the model having the ??. Is there ayny scenario where today I'd use a trick to create a block, do the binding, clone the thing, use that in the closure. Is there any case where I can't use .use? That's the experiment I ran in my had and I'm struggling with an example where if I had the .use syntax rather than using the clone dance. Josh: Sounds like what you're describing is you want the smart capture behavior, the way it "floats" to the top of the closure and doesn't require doing it before the closure. If we had a separate "heavyweight" clone syntax that has the same "floating to the top" smart-capture behavior, would you still feel like there's no use case? e.g. ```rust let s = String::new(); let x = move || { consume(s.clone()) // what if this cloned as part of the closure capture ("1 extra clone"). }; // or what if there was some explicit syntax for this, such as making `clone` a keyword... ``` TC: That's an interesting clarification. If we can't distinguish these. There's an orthogonal piece here: is the closure capture tied to whether the clone is lightweight. What worries me is having this nice feature and then not using this nice feature because the upstream didn't opt you in by not calling something 'lightweight'. Niko notes: *Hypothesis:* This distinction is "squishy" and people will feel unclear about where/if it can be applied. Aapo: Someone could argue `Copy` is already squishy. You can use a big array, copy it and that can be surprisingly expensive. I like bothe `.use` and autoclone ideas. But `.use` is definitely the conservative option. If you go with autoclone on nightly, maybe go as far as stabilizing it and then it turns out it's leading us in really bad places, getting rid of it is really hard. But from `.use` it's easy to move to autoclone. Hypothetically it could lead to Rust being the language that will leak all the memory for you if you make a small mistake. Niko: I noted a hypothesis there TC that this feels squishy. I noted it down. There are two extensions that could be done in this design. I had an RFC that would allow you to do this (clone on entering a closure; would make it lighter but not as light): ```rust let x = move(s.clone()) || { // short for `s = s.clone()` consume(s.clone()) }; ``` Niko: When I'm writing shellscripts in Rust, I'm cloning vecs left and right. I don't mind writing the clone there. Niko: I think we can peel out bits of the design. One is the keyword. Another is smarter closure captures as opposed to a different kind of closure. Tyler: I wanted to add one hyopthesis: I've debugged arc cycles in Swift and other languages and it's been frustrating. One distinction is that Swift you have implicit cloning and implicit arc. In Rust we can hide the arc inside. I wonder if we can lean on that level of explictness more. Many languages have garbage collections and implicit Arcs don't have any explicit syntax on the type definition. Hypothesis: Type structure will help you debug these kinds of issues. Niko notes (not vocally): *thought* experiment of a rust-analyzer mode where you can click "reveal" and it shows you the `.use` TC: Hypothesis: the deferred rc pattern: how can we push that pattern to it be more convenient. Another hypothesis: upstream needs to decide what's lightweight or not. Whether the complexity is worth it. Niko: I did think Nadri's observation was interesting. I don't entirely buy that the connection between smart pointers and this trait is the only place (e.g., `Bytes`). But there might be an intuition there. Josh: It's worth calling out to the Bytes example. There is a type `Bytes` widely used in the async ecosystem. An arc with a bug buffer. You can pass around chunks of the buffer and ??. There are explicit calls you can make that's not jsut do the full clone, but get a subset of the buffer. But you also potentially want to treat this as lightweith cloneable, because it can be very cheap like arc. But there is this distinction and the explicitness there is important so you can change it to a better method. Josh: ArcRef which we were talking about adding for field projection has the same concern. If you have an ArcRef, you need to also be able to decide when you want to use it lightweight and when to get rid of the huge data attached. nikomatsakis (types): At AWS, in my view, this is the kind of distinction I *do* want ex-Java-devs to learn and think about. *Prediction (nikomatsakis):* If we introduce the automatic-is-the-new-copy type, then I suspect it will become "folk wisdom" to turn it off because of leaks like this. (not hypothesis beacuse I don't know how to test it) *Hypothesis (tmandry)*: Some types like `Bytes` want explicit lightweight cloning because of the intended use case, while others want mostly implicit. ## Other things that need "pre-closure/loop/block" runtime simulacrum: I've relatively commonly had a bunch of initialization work to do before I hit some kind of loop or -- commonly -- closure. E.g., registering metrics, setting up some kind of state. Often that ends up with the "same" pattern as with clone, where you invent some name and write `let x = ...; let y = ...; ... spawn(async move { do_something(x, y); })`. The proposal here seems very focused on *clone* but in practice that ends up solving maybe 60-80% of the problems I hit here. Is there an extension we could look at to solve the broader class? (I, and others, had proposed some form of `super { ... }` to break out of closures on the RFC). This also comes up even with just clone, where e.g., I have a struct like `struct Foo { name: String, big_value: Arc<Foo> }` and I'm OK cloning the String repetitively, but that's definitely not a "lightweight clone" in the same way that just incrementing one ref count is. nikomatsakis: Can you give a more concrete example? I can't think of cases off the top of my head. simulacrum: code like this: ```rust let rejected_conns = metrics .registry() .register_counter("RejectedFdLimitExhausted".into(), None); let monitor = metrics .registry() .register_task_monitor("OutboundTcpListenerNanny"); let conn_task_monitor = metrics .registry() .register_task_monitor("OutboundTcpListener"); // ... often a half dozen other metrics ... tokio::spawn(async move { while let Ok(sock) = listener.accept() { let rejected_conns = rejected_conns.clone(); let monitor = monitor.clone(); let conn_task_monitor = conn_task_monitor.clone(); tokio::spawn(async move { // use them. Ideally, I'd like to be able to write this as something like: // i.e., avoid the multiple layers of indirection and needing to invent names duplicating the metric name // this is particularly tricky because of the double-spawn case, but single-spawn is not uncommon too. handle_conn(&sock, super { metrics.registry()... }.clone()); // What does this look like with use? }); } }) ``` nikomatsakis: Wouldn't these be lightweight clones? In which case, I assume it would be ```rust let rejected_conns = metrics .registry() .register_counter("RejectedFdLimitExhausted".into(), None); let monitor = metrics .registry() .register_task_monitor("OutboundTcpListenerNanny"); let conn_task_monitor = metrics .registry() .register_task_monitor("OutboundTcpListener"); tokio::spawn(async move { while let Ok(sock) = listener.accept() { tokio::spawn(async move { rejected_conns.use }); } }) // desugars to: tokio::spawn(async move(rejected_conns = rejected_conns.use) { while let Ok(sock) = listener.accept() { tokio::spawn(async move(rejected_conns = rejected_conns.use) { rejected_conns.use }); } }) // the worrisome case to Niko is this: foo.map.unwrap_or_else(|| x.use) // you don't want that closure to capture ``` Or with the explicit capture clauses it's mildly better ```rust tokio::spawn(async move(rejected_conns = rejected_conns.registry().register_counter(...)) { while let Ok(sock) = listener.accept() { tokio::spawn(async move { rejected_conns.use }); } }) ``` Niko: this kind of reminds me of Go's `defer`. Maybe that's how the capture clause should work. At the point there you say "I want to capture it and use it" and then the compiler can track it. One thing it doesn't solve is when I'm using closures, it's hard to tell what it captures. *Ah* this is what the `super` was meant to be: ```rust tokio::spawn(async move { while let Ok(sock) = listener.accept() { tokio::spawn(async move { super { rejected_conns.use } }); } }) ``` Appo: Moving the rejected_cons into the closure somehow the same way you have const block, saying whatever happens here happens outside the closure. That would be an option: you could see it, find it happening in the code. You can inspect it. Niko: I think that's what the explicit capture clauses were doing. Appo: Like the async move rejected_cons? Niko: I think there's a chance the `.use` syntax on its on will be useful enough for usecases like this. simulacrum: It's not entirely clear to me that `super` is that different. You could imagine `.super.clone()` or `super clone()`. I personally never ran into "cloning something makes me want to put it into an Arc". It feels the lightweightness is not what I want the compiler to yell at me for _in the moment_. Later on for perf etc. yes. But not in the momemnt. How Niko understands Mark: Many performance-sensitive users would want automatic clones and then expect to profile to find the ones that matter. What Niko wants to say so he doesn't forget: -- I think use is "context-free" in a way that is very different than `super` (you don't have to know you're in a closure to know it's the right thing to do). Tyler: simulacrum, are you saying these lines where your creating metrics counters you'd want to move them outside the closure into the superblocks? simulacrum: Today the pattern is: I have to do let maybe 50 lines above and any async move / move closure I need to add `let x = x.clone()`. I want to write the ergonomic thing. I don't want to go read a different file somewhere else, I want to see it all locally. Tyler: Counter point: I want to see the control flow as it actually happens. simulacrum: modulo panics, there's no control flow here in this "uplifting", since the middle text is all linear, essentially. ## Linting on automatic clones of specific bindings > However, users would have to _know_ that they should use this lint. The proposed fix for this was to have an annotation on methods like `get_mut` like "should_disable_automatic_clones". This would issue a warning if `get_mut` is called in a context where `automatic_clones` is set to allow. Kind of weird, but as a result: > ```rust let mut rc1 = Rc::new(vec![]); let v = print_values(rc1); Rc::get_mut(&mut rc1).unwrap(); // <-- WARNING: you should disable automatic clones fn print_values(v: Rc<Vec<u32>>) -> Rc<Vec<u32>> {...} ``` tmandry: This doesn't seem like the strongest version of this feature. The version I would like to see would lint on implicit clones that happened _on the receiver of `Rc::get_mut`_. So in the above example the lint would require you to write `print_values(rc1.use)` and nothing else: no additional `#[warn]` or anything like that. Is there a reason we can't implement this? I think a lint with false positives would still be quite useful for those rare situations where you call something like `Rc::get_mut`. nikomatsakis: Seems implementable—I guess the question is how often the autoclone would occur on that particular handle vs a handle somewhere else.nikomatsakis: Specifically what I imagine is like `do_something(x); do_something_else(x);` where `do_something_else` does the `get_mut` internally, but it's the call to `do_something(x)` that is causing the clone.I was