-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify lightweight clones, including into closures and async blocks #3680
base: master
Are you sure you want to change the base?
Conversation
Personally, I don't feel that the non-closure/block use cases of this are really strong enough to warrant adding this, and the closure/block use case can be fixed with The example 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 could just be written as Which can presumably be solved either by making LLVM smarter about atomics for the specific case of The ergonomics of needing to clone in a block are annoying though, I agree, but that's a smaller feature by being able to do: spawn(async clone {
func1(x).await;
func2(y).await;
}); and similarly for closures. |
The problem with a Even without the
I think there's potential value there (and I've captured this in the RFC); for instance, we could omit a clone of a |
I'm afraid I'm pretty negative about this RFC.
|
I've added a new paragraph in the "Rationale and alternatives" section explaining why
|
@Diggsey wrote:
You can; I'm not suggesting that we couldn't provide a syntax for that, too. However, people have asked for the ability to distinguish between expensive and lightweight clones. And a lightweight clone is less of a big deal, making it safer to have a lighter-weight syntax and let users mostly not worry about it. We could additionally provide syntax for performing expensive clones; I've mentioned one such syntax in the future work section, but we could consider others as well if that's a common use case.
That assumes that users want to call
This is only true if we omitted the proposed elision behavior, or if we decide that it's acceptable for methods to have elision semantics attached to them. I agree that in either of those cases there's no particular reason to use a special syntax rather than a method.
This is a reasonable point. I personally don't think this would cause problems, but at a minimum I'll capture this in the alternatives section, and we could consider changing the elision behavior to make it required. The annoying thing about making it required is that we then have to implement it before shipping the feature and we can never make it better after shipping the feature. I don't think that's a good tradeoff. Ultimately, though, I think the elisions aren't the most important part of this feature, and this feature is well worth shipping without the elisions, so if the elisions fail to reach consensus we can potentially ship the feature without the elisions. (Omitting the elisions entirely is already called out as an alternative.)
See the previous points about people wanting to distinguish lightweight clones specifically. This is a load-bearing point: I can absolutely understand that if you disagree with the motivation of distinguishing lightweight clones, the remainder of the RFC then does not follow. The RFC is based on the premise that people do in fact want to distinguish lightweight clones specifically.
I've added this as an alternative, but I don't think that would be nearly as usable. |
@Diggsey wrote:
While I don't think this is dangerous, I do think it's not the ideal solution, and I'd love to find a better way to specify this. The goal is to |
Thank you for working on this RFC! PyO3 necessarily makes heavy use of Python reference counting so users working on Rust + Python projects may benefit significantly from making this more ergonomic. The possibility to elide operations where unnecessary is also very interesting; while it's a new idea to me, performance optimizations are always great! I have some questions:
|
Since "Precise capturing" #3617 also abuses the |
We should really not overload the usage of the keyword Isn't it easier to understand if we've some macro for the multiple clones that run before the code that consumes them, but still inside some distinguished scope?
In this, the
We use this multi-clones pattern outside async code too, so this non-async specific approach benefits everyone. |
The point @xkr47 is making is one of my biggest concerns with this idea, added complexity to be taught aside. One of the reasons I use Rust is that, from the perspective of managing memory consumption, it has fewer footguns than a garbage collected language. (And I've run into stuff on more than one occasion where a "memory leak" turned out to be some piece of JavaScript forgetting to unhook an HTML DOM event handler, leading to stale stuff being spuriously held alive by some closure.) If cloning |
For the cases where you do want to see every Rc clone, you can just |
Better than nothing... though my much-earlier-mentioned concerns about knock-on effects still apply. (That the ecosystem as a whole will become less memory efficient due to a more "laissez-faire by default" approach toward having the compiler surface these concerns. As C and C++ demonstrated very clearly, it's not as if it's practical for you to take responsibility for bringing your entire transitive dependency tree up to whatever standard you want to hold to. Rust has |
This RFC uses as motivation the ergonomics of cloning instead of moving into closures and async blocks. It solves this by creating The RFC also proposes new syntax Some of these optimisations may be desired, and could be implemented for What is the current status of this RFC? There remain unresolved review comments, and no FCP has started, but PRs have popped up in nightly and syn to begin implementation |
As I already said in more words a while back (#3680 (comment)), this RFC mixes up two distinct issues which should be handled separately. I'd appreciate if it could be closed and superseded by two separate RFCs to each deal with one issue more fully. |
Coming across this for the first time, I do agree that cloning things into closures is unergonomic, but this RFC is the wrong solution. The entire "cheaply cloneable" concept is a solution to a footgun that is only introduced by the solution of automatically cloning closure variables. If, for example, you consider a closure syntax where you explicitly mention the variables to clone, the footgun goes away. And I'd honestly prefer a The name "Use" is awful. The word "Use" can mean so many different things, and aliasing it with "clone" is extremely unclear. If a non-Rust programmer comes across Related, I don't like the addition of the |
Hi folks, I just took an hour to read every comment on this thread. I wanted to summarize the various bits of feedback to try and capture the main points that were made. I would break things into three buckets. I'd love to hear if you think there is any major point that I missed here. The Good (points of general agreement)capturing ref counted values in closures and async blocks is a pain pointMany folks (though not all) agreed that the state of capture in today's closures is indeed a pain point. David Hewitt from PyO3 also highlighted that "PyO3 necessarily makes heavy use of Python reference counting so users working on Rust + Python projects may benefit significantly from making this more ergonomic". The Bad (points of disagreement with the RFC)"Can't serve two masters"The gist of this concern is that, although the RFC declares a motivation of making things easier for new users, adding a new syntax like
Similar comments were made by boats and matthieu-m. In David Hewitt's comment, he also requested further justification for why one needs to write "Cheap is in the eye of the beholder"Several people pointed out that it is difficult to define what is "cheap". This led Dirbaio to propose "use-site" declarations like Another alternative (floated by Mark-Simulacrum and cynecx) was to provide some kind of "inline syntax" like "Optional" optimizations are creepyThere was some disagreement about whether the idea of optimizing and the Ugly (bikeshed-like concerns)The name
|
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Re: "We could have a syntax for cloning into a closure", I'm not sure it's fair to call it a "bikeshed-like concern" when talking about whether Rust should have a new hole in its preference for making behaviours explicit at the call site. It's bad enough that we have |
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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This talks about the compiler, but in std we'd likely also apply specializations to elide clones that are Use
. Such specializations tend to have a somewhat broader scope than what the compiler can prove.
So I think trying to precisely define what the compiler will do could be misleading to users when additional optimizations elide more than what's mentioned here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I guess technically it won't be Use
itself because it can't be a specialization trait if it inherits from Clone
(😥) but yet another internal specialization trait quite like TrivialClone
(though that one implies bit-copyability).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, if the compiler wants to use the presence of this trait for optimizations, won't it also have problems with lifetime-dependent implementations just like specialization?
…1, r=nikomatsakis Ergonomic ref counting This is an experimental first version of ergonomic ref counting. This first version implements most of the RFC but doesn't implement any of the optimizations. This was left for following iterations. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang#132290 Project goal: rust-lang/rust-project-goals#107 r? `@nikomatsakis`
…1, r=nikomatsakis Ergonomic ref counting This is an experimental first version of ergonomic ref counting. This first version implements most of the RFC but doesn't implement any of the optimizations. This was left for following iterations. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang#132290 Project goal: rust-lang/rust-project-goals#107 r? ``@nikomatsakis``
…1, r=nikomatsakis Ergonomic ref counting This is an experimental first version of ergonomic ref counting. This first version implements most of the RFC but doesn't implement any of the optimizations. This was left for following iterations. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang#132290 Project goal: rust-lang/rust-project-goals#107 r? ```@nikomatsakis```
Rollup merge of rust-lang#134797 - spastorino:ergonomic-ref-counting-1, r=nikomatsakis Ergonomic ref counting This is an experimental first version of ergonomic ref counting. This first version implements most of the RFC but doesn't implement any of the optimizations. This was left for following iterations. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang#132290 Project goal: rust-lang/rust-project-goals#107 r? ```@nikomatsakis```
…1, r=nikomatsakis Ergonomic ref counting This is an experimental first version of ergonomic ref counting. This first version implements most of the RFC but doesn't implement any of the optimizations. This was left for following iterations. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang#132290 Project goal: rust-lang/rust-project-goals#107 r? ```@nikomatsakis```
Should we allow *any* `x.clone()` call to be elided if a type is `Use` (or `Use | ||
+ Copy`)? If we did this, that would not give us a way to explicitly avoid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we allow *any* `x.clone()` call to be elided if a type is `Use` (or `Use | |
+ Copy`)? If we did this, that would not give us a way to explicitly avoid | |
Should we allow *any* `x.clone()` call to be elided if a type is `Use` (or | |
`Use + Copy`)? If we did this, that would not give us a way to explicitly avoid |
…matsakis Ergonomic ref counting This is an experimental first version of ergonomic ref counting. This first version implements most of the RFC but doesn't implement any of the optimizations. This was left for following iterations. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang/rust#132290 Project goal: rust-lang/rust-project-goals#107 r? ```@nikomatsakis```
…matsakis Ergonomic ref counting This is an experimental first version of ergonomic ref counting. This first version implements most of the RFC but doesn't implement any of the optimizations. This was left for following iterations. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang/rust#132290 Project goal: rust-lang/rust-project-goals#107 r? ```@nikomatsakis```
use(x = x.method(), y) || { ... } | ||
|
||
// `..` additionally captures everything that a plain `use` would | ||
async use(x = &obj, move y, ..) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this idea. If someone in the project wanted complete transparency, an option could be added to Clippy to prevent the use of use || { ... } without explicitly specifying the variables that should be cloned (or moved).
`use(ref x)`). | ||
|
||
This potentially makes `use` closures/blocks almost completely supersede `move` | ||
closures; we could consider in the future whether we want to deprecate them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A very ambitious idea. I like it :) Once this approach is implemented, I would start using use instead of move in my projects because it would offer much greater possibilities and make the code more explicit.
As has been said before, a way to explicitly annotate the closure capture kind (move/clone) of a variable (or all variables) sounds like a fine solution for the "reducing friction" part of the RFC. This syntax would feel like a natural expansion to the current move-capturing one: After this, there could be a conversation about adding another capture kind for light clones in the future. |
… r=<try> Ergonomic ref counting: optimize away clones when possible This PR build on top of rust-lang#134797. It optimizes codegen of ergonomic ref-counting when the type being `use`d is only known to be copy after monomorphization. We avoid codening a clone and generate bitwise copy instead. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang#132290 Project goal: rust-lang/rust-project-goals#107 r? `@nikomatsakis` This PR could better sit on top of rust-lang#131650 but as it did not land yet I've decided to just do minimal changes. It may be the case that doing what I'm doing regress the performance and we may need to go the full route of rust-lang#131650. cc `@saethlin` in this regard.
I think it's worth mentioning that the |
I just re-read this entire conversation again: People seem unsure about the There seems to be (almost) a consensus that an explicit/precise capture syntax would be welcome, and maybe even make further ergonomics unnecessary. Yet, I don't think there are any active proposals for this. I think this should probably be split into a separate RFC, since I believe there's a good chance to get that accepted faster. I'd be happy to write an RFC for precise closure capures if there is interest? |
… r=<try> Ergonomic ref counting: optimize away clones when possible This PR build on top of rust-lang#134797. It optimizes codegen of ergonomic ref-counting when the type being `use`d is only known to be copy after monomorphization. We avoid codening a clone and generate bitwise copy instead. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang#132290 Project goal: rust-lang/rust-project-goals#107 r? `@nikomatsakis` This PR could better sit on top of rust-lang#131650 but as it did not land yet I've decided to just do minimal changes. It may be the case that doing what I'm doing regress the performance and we may need to go the full route of rust-lang#131650. cc `@saethlin` in this regard.
Provide a feature to simplify performing lightweight clones (such as of
Arc
/Rc
), particularly cloning them into closures or async blocks, whilestill keeping such cloning visible and explicit.
A very common source of friction in asynchronous or multithreaded Rust
programming is having to clone various
Arc<T>
reference-counted objects intoan 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:
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.
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.
This RFC is part of the "Ergonomic ref-counting" project goal, owned by
@jkelleyrtp. Thanks to @jkelleyrtp and @nikomatsakis for reviewing. Thanks to
@nikomatsakis for key insights in this RFC, including the idea to use
use
.Rendered