-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Lint for undesirable, implicit copies #45683
Comments
I think the lint would work roughly like this. We define a set of types that should not be implicitly copied. This would include any type whose size is >N bytes (where N is some arbitrary number, perhaps tunable via an attribute on the crate), but also any struct that is tagged with We would then comb code for implicit copies. This is (somewhat) easy to define at the MIR level: any use of a user-defined variable (not a temporary) that is of a type implementing let y = x;
foo(x);
let y = [x; 22]; // at least I think this would count =) One interesting case that would, by this definition, be linted against is this:
This one is interesting because, unlike the others, there is no way to readily silence the lint, except by adding an It is also interesting that the use cases here perhaps breakdown into two categories:
In fact, I would argue that the latter is the majority case, so maybe we just want to separate this into two lints, or else say that structs with the let z = {
let x0 = x.clone();
move || use(x0) // this is a last-use of `x0`, hence no warning
};
use(x); // uses original `x` |
I would prefer it if this was more specific, e.g. |
You don't want to warn on any use - that would warn on moves too. You want to warn on uses where the variable is live after the use. |
@arielb1 indeed. |
@eddyb do you have a specific example of something you would want to warn, and something you would not want? |
@nikomatsakis Copying a |
@eddyb it seems like we could generalize then to a lint that works like this:
Maybe a better name than |
Anyway, regardless of the nitty gritty details, let me leave a few pointers here for how we might implement this. @cramertj is hopefully gonna take a look. First off, let's look a bit at MIR. MIR definition is here.. A crucial concept I referenced above is a Lines 1231 to 1243 in 4e3901d
The arguments to MIR statements are always operands, which explicitly identify a copy: Lines 1399 to 1403 in 4e3901d
I think right now we will produce a One way to do that is with the MIR visitor, which lets you quickly walk the IR. In particular we'd probably want to override the rust/src/librustc/mir/visit.rs Lines 142 to 146 in 4e3901d
There is code for computing liveness -- basically, whether a variable may be used again. That code lives in rust/src/librustc_mir/util/liveness.rs Lines 117 to 120 in 4e3901d
but that only gives you values at basic block boundaries. You will want rust/src/librustc_mir/util/liveness.rs Lines 161 to 167 in 4e3901d
So I guess that roughly the idae would be to identify copies where the value's type is marked to be linted. We would then compute the liveness at the point of copy and see if the Some annoying challenges:
Still, this is a lint after all, it doesn't have to be totally precise. I think that for the first version of the code I would only worry about local variables and ignore more complex |
@qmx I started https://github.com/cramertj/rust/tree/copy-lint if you want to take a look. Not sure how useful it is-- I only got the skeleton. |
Same for EDIT: As @rkruppe pointed out, "thread" doesn't make sense here. That doesn't change the problem though, see below. |
RefCell is not Sync so how could you have a |
This can all happen in one thread. |
let r = RefCell::new(42);
let r1 = &r;
let r2 = &r;
let _mutref = r1.borrow_mut();
let illegal_copy = *r2; // copying while there is an outstanding mutable reference |
Oh yeah you're right. The mention of another thread really mislead me there. |
Sorry for that, not sure what I thought. "Another function" is really what I meant (though it can be the same, as in my counterexample). |
@RalfJung in retrospect, the RefCell problem is obvious -- basically, of course copying is bad, for the same reason that |
Just found out about rust-lang/rfcs#2407 - that would be a nice suggestion for the linter in the future, wouldn't it? |
Just as a data point, this issue affects real world programs. Recently rav1e has eliminated an implicit copy of a large type and gained speed thanks to it.
|
Just wanted to note that we did add a lint that highlights all moves larger than a configured limit #83519 |
As part of #44619, one topic that keeps coming up is that we have to find some way to mitigate the risk of large, implicit copies. Indeed, this risk exists today even without any changes to the language:
In addition to performance hazards, implicit copies can have surprising semantics. For example, there are several iterator types that would implement
Copy
, but we were afraid that people would be surprised. Another, clearer example is a type likeCell<i32>
, which could certainly be copy, but for this interaction:For a time before 1.0, we briefly considered introducing a new
Pod
trait that acted likeCopy
(memcpy is safe) but without the implicit copy semantics. At the time, @eddyb argued persuasively against this, basically saying (and rightly so) that this is more of a linting concern than anything else -- the implicit copies in the example above, after all, don't lead to any sort of unsoundness, they just may not be the semantics you expected.Since then, a number of use cases have arisen where having some kind of warning against implicit, unexpected copies would be useful:
Copy
Cell
,RefCell
, and other types with interior mutability implementingCopy
#[derive(PartiallEq)]
and friends with packed structs&T
toT
(i.e., it'd be nice to be able to dofoo(x)
wherefoo: fn(u32)
andx: &u32
)I will writeup a more specific proposal in the thread below.
The text was updated successfully, but these errors were encountered: