Skip to content
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

split the Copy trait in two traits: Pod and Copy #936

Closed
wants to merge 3 commits into from

Conversation

japaric
Copy link
Member

@japaric japaric commented Mar 4, 2015

@glaebhoerl
Copy link
Contributor

What is the reasoning behind "iterators shouldn't be Copy"?

@Gankra
Copy link
Contributor

Gankra commented Mar 4, 2015

@glaebhoerl Weird cases where you think you changed an iterator but didn't because you implicitly copied it somewhere. It's an inherently stateful thing, which suggests it shouldn't be Copy'd implicitly.

+1 for Pod distinction. +100 for derive-inheritance.

@nikomatsakis
Copy link
Contributor

Without having read this, I'm in favor =)

@glaebhoerl
Copy link
Contributor

@gankro Could you link me the relevant discussion?

@japaric
Copy link
Member Author

japaric commented Mar 4, 2015

@glaebhoerl The problem seen in practice is explained in the last part of this rust-lang/rust#21809 (comment) . Also see rust-lang/rust#18045, and rust-lang/rust#21846 for previous discussion.

@aturon
Copy link
Member

aturon commented Mar 5, 2015

@japaric Delighted to see this! I like the design, and am definitely in favor of introducing this distinction.

I do agree that the derive situation here is getting a bit hairy, and of course would, like you, prefer to provide blanket implementations. But since that's not really on the table, the "more magic" derive alternative sounds pretty good to me. We've talked about doing similar things with Ord and Eq.

👍

@glaebhoerl
Copy link
Contributor

@japaric Thanks!

@nikomatsakis
Copy link
Contributor

I am in favor of the general idea. However, there is a missing alternative. We could add a Pod trait but make it builtin, like Sized, so that it need not be manually implemented. (Note that while it is not presently supported, we can also make it possible to opt-out of Sized and Pod.)

In fact, it need not be builtin, we can just use OIBIT (something like this):

unsafe trait Pod { }
unsafe impl Pod for .. { }
unsafe<'a,T:?Sized> impl Pod for &'a T { }
unsize<'a,T:?Sized> impl !Pod for &'a mut T { }
unsafe<T:?Sized> impl Pod for *const T { }
unsize<T:?Sized> impl !Pod for *mut T { }
unsafe impl<T:Drop> !Pod for T { }

The caveat here is that I don't know that the semantics of T:Drop were carefully considered. In this case, I mean that T is a type for which Drop was implemented. That is the current behavior in any case, and I think a very useful one (there were other OIBIT cases where this was what I wanted to say).

UPDATE: Edited the set of rules above slightly.

@tbu-
Copy link
Contributor

tbu- commented Mar 5, 2015

@nikomatsakis I think there shouldn't be a difference between the Podness of *const T and *mut T – unlike &mut T vs &T they're not that different and are used almost interchangebly in the standard library.

@japaric
Copy link
Member Author

japaric commented Mar 5, 2015

In fact, it need not be builtin, we can just use OIBIT (something like this):

I really like this idea for Pod. I think it makes impl Clone for T where T: Pod more feasible, for instance it would make any arity tuples and any arity arrays cloneable (I think).

How would Copy work with that approach? If we want to make it a "normal trait" (nor OIBIT nor built-in) we wouldn't be able to impl<T: Copy> Copy for [T; N] {} for every value of N nor impl Copy for (A, B, ..) where A: Copy, B: Copy , .. for all arities, which would break today's code, but those issue can be addressed in the future with integer as generic parameters (arrays) and variadic generics (tuples). Or we could keep Copy in its current form: built-in rules + manual impls.

(I agree with @tbu- that *mut T should be Pod)

@pnkfelix
Copy link
Member

pnkfelix commented Mar 5, 2015

@nikomatsakis Taking the OIBIT approach for Podintroduces the problem that the Pod-ness of a type is an implicit part of its public interface, which is part of what we were trying to correct when we changed things to make Copy opt-in, no?

(I guess this is no different than the situation with other OIBIT traits like Send and Sync...)


Just to clarify: The issue I am concerned about with hidden implicit properties of public interfaces is that conditional cfg and/or changes between versions of code can cause one of the implicit properties to go away without the developer realizing it. (But again, as I said above, this is the current situation for Send and Sync, and we deliberately chose to go down this route.)

@nikomatsakis
Copy link
Contributor

Yes. I should have mentioned that as a downside. I am not sure whether pod-ness should be an implicit part of the public interface or not. Probably not, since I tend to think that adding a Box ought to be something types can "just do" unless they have opted into something (like Copy) which rules it out. 

-------- Original message --------
From: Felix S Klock II notifications@github.com
Date:03/05/2015 10:03 (GMT-05:00)
To: rust-lang/rfcs rfcs@noreply.github.com
Cc: Niko Matsakis niko@alum.mit.edu
Subject: Re: [rfcs] split the Copy trait in two traits: Pod and Copy
(#936)

@nikomatsakis Taking the OIBIT approach for Podintroduces the problem that the Pod-ness of a type is an implicit part of its public interface, which is part of what we were trying to correct when we changed things to make Copy opt-in, no?

(I guess this is no different than the situation with other OIBIT traits like Send and Sync...)


Reply to this email directly or view it on GitHub.

@nikomatsakis
Copy link
Contributor

Something I'd like to have spelled out in the RFC -- in what cases is this backwards incompatible if we do it after 1.0?

@nikomatsakis
Copy link
Contributor

On Thu, Mar 05, 2015 at 05:53:21AM -0800, tbu- wrote:

@nikomatsakis I think there shouldn't be a difference between the Podness of *const T and *mut T – unlike &mut T vs &T they're not that different and are used almost interchangebly in the standard library.

I agree. I didn't mean to suggest there would be one.

@theemathas
Copy link

@eddyb seems to dislike this, and even opt-in Copy. Additionally, there seems to be a disagreement on what does Copy mean.

From reddit:

I personally consider opt-in Copy to be one of the 1.0 mistakes - I sadly weren't able to implicate myself in the decision process for it, nor did I have a better alternative at the time.
Opt-in Copy was designed to protect from unsound unsafe code, which largely means pointers.

From IRC:

theme> I interpret Copy to be "copying is implicit, cheap and does what you expect"
...
eddyb_> theme: that's wrong
eddyb_> the definition of Copy is "using lvalue as rvalue leaves safe, valid value behind"

Here is the start of that IRC conversation.

@japaric
Copy link
Member Author

japaric commented Mar 7, 2015

Something I'd like to have spelled out in the RFC -- in what cases is this backwards incompatible if we do it after 1.0?

AFAICT, both the proposed change and the #[derive] alternative break today's code [1] but are backward compatible (i.e. no interface will break) and could be implemented post 1.0. To elaborate: Since a type that implements the new Copy must also implement Pod, it will be equivalent to today's Copy. Also, relaxing Cell's bound to T: Pod would continue allowing Copy types and it would additionally allow Pod types.

A backward incompatible change would be changing a type from being Copy to Pod, but that's a library decision and not a direct consequence of implementing this language change. Also, this RFC doesn't propose any change like that in the stdlib.

[1] Breaking changes and upgrade path:

The proposed change does break today's code. Manual impl Copy for Foo would require an additional impl Pod for Foo, but these manual impls are a few in comparison to #[derive(Copy)]s (I count ~10 vs ~800 in the rust repo).

The #[derive] alternative would also break today's code. #[derive(Copy, Clone)] would need to be changed to #[derive(Copy)]. Even though the number of broken LoC is much higher in this case (I count ~200 in the rust repo), the code can be easily fixed with sed.

I consider both of these breaking changes to be tolerable post 1.0 and in line with the "stability caveats" outlined in this blog post. I also think it'd be possible to ship a "rustup" script with the next point release that fixes most/all of these breaking changes.

@japaric
Copy link
Member Author

japaric commented Mar 7, 2015

Some more thoughts on OIBIT Pod (@nikomatsakis proposed alternative):

I think OIBIT Pod is a better default:

  • Things that can be be Pod will be Pod i.e. you can't forget to impl Pod them.
  • Things that shouldn't be Pod, e.g. wrapper on a Box<T>, won't be Pod by default, but you still can opt-in via unsafe impl (you don't have this choice with the original RFC)
  • The downside is that if you to make an affine type without destructors you have to remember to opt-out from Pod. This case seems rare - only Rngs like iOS's OsRng { _dummy: () } come to mind. Can we assume that the programmers will remember to opt-out given that they are writing something so "uncommon"? I don't think we should.

The biggest issue I see with OIBIT (in general) is that you can "implicitly" remove the Podness of a struct by adding/changing a field without getting a hint from the compiler:

  // OIBIT
  struct IsPod {
      a: char,
+     b: String,  // `Pod`ness is lost without a warning
  }

vs

  struct IsPod {
      a: char,
+     b: String,
  }

  impl Pod for IsPod {}
+ //~^ error: can't implement `Pod` because the `b` field is not `Pod`

Removing Pod from a struct is a breaking change, and since OIBIT doesn't give a hint you may end up releasing a bugfix version with a breaking change in it. This could be addressed with some tool (cargo?) that enforces semver checks between two crate versions.

@sfackler
Copy link
Member

sfackler commented Mar 7, 2015

I am a fan of opt in Pod as well.

@eddyb
Copy link
Member

eddyb commented Mar 7, 2015

Why would we be keeping Copy around if Pod is what Copy should have been, then?
Can we not handle the cases where it might not be intentional (the reason why Range doesn't implement Copy, presumably) with lints?
Why does it have to be so special?

@eddyb
Copy link
Member

eddyb commented Mar 7, 2015

AFAIK, unsafe impl Copy for Foo, where Foo contains fields which may have dtors, would easily lead to UB, so I would deny it - or maybe lint against that case by default, it could be a mistakenly left out Copy bound on a type parameter.

@Gankra
Copy link
Contributor

Gankra commented Mar 7, 2015

@japaric Thanks for breaking down the current situation. Even if it's "acceptable" breakage, I'd like to see this land pre-1.0 so libraries can properly audit if their Copy's should really be Copy.

@seanmonstar
Copy link
Contributor

Here's an instance where an object that is "pod" is explicitly not made Copy: https://github.com/servo/string-cache/blob/master/src/atom/mod.rs#L163 This is because, even though the data is just a u64, it's reference counted to keep the backed data in a HashMap.

This RFC would propose that an Atom would be Pod, and that any structure containing an Atom would be free to implement Copy on it. This would bypass the reference counting of Atom.

@eddyb
Copy link
Member

eddyb commented Mar 7, 2015

@seanmonstar it would at least require the use of an unsafe impl, or it could be denied, given that Atom implements Drop (but the unsafe code could transmute to/from u64 if it wanted to, so that's a counter-argument to denying the unsafe impl in that case).

@japaric
Copy link
Member Author

japaric commented Mar 7, 2015

@eddyb

Why would we be keeping Copy around if Pod is what Copy should have been, then?

If we remove Copy and only provide Pod, everything will have to be explictly cloned, as nothing could be marked as implicitly copyable (Copy). Is that what you really mean?

Can we not handle the cases where it might not be intentional (the reason why Range doesn't implement Copy, presumably) with lints?

Yes, that's an option that has been mentioned before. See the last part of rust-lang/rust#21846 (comment), and the following comment.

Why does it have to be so special?

Range is just a particular case. This RFC provides a middle ground (Pod) between non implictly copyable and implictly copyable, such that actually more types can opt-in to implictly copyable, as the only requirement for that is the Podness of its fields, and Pod is more likely to be implemented than Copy for elegible types.

AFAIK, unsafe impl Copy for Foo, where Foo contains fields which may have dtors, would easily lead to UB, so I would deny it

I agree. It's already marked as unsafe, but I'd be in favor of a lint, denied by default.

@seanmonstar

This RFC would propose that an Atom would be Pod.

No. Atom doesn't match the definition: "Pod indicates that a type can be cloned by just copying its bits". Because your Clone implementation involves refcounting and is not a simple memcpy. Also, the compiler won't even let you impl Pod for Atom because Atom has a destructor, and today's Copy rules (you can't impl Copy for Dropable) carry over to Pod. I'll update the RFC to explictly state that, and add a cfail test for impl Pod for Atom to the implementation PR.

Even the OIBIT alternative would not mark Atom as Pod because of the impl<T: Drop> !Pod for T. You still can unsafely opt-in Atom to be Pod, but as I mentioned before I'd prefer to deny lint that.

@tbu-
Copy link
Contributor

tbu- commented Mar 10, 2015

@RalfJung Do you talk about Ranges? If so, only implementing IntoIterator made some ergonomics worse.

@RalfJung
Copy link
Member

You are right, I meant Range and updated my comment accordingly.

Well, if I followed this discussion correctly then any solution would make ergonomics worse. Certainly, having another trait harms ergonomics. Also, I don't see why type inference couldn't be improved to handle the case you gave at rust-lang/rust#21846 (comment). And finally, writing something like

fn a(x: u32) {}

for i in (0..10u32) { a(i) }

until, later, type inference is improved, doesn't strike me as too bad either.

(Sorry if this is the wrong place to discuss that. It seems to me the issues are entangled enough that it ends up being the same discussion.)

@tbu-
Copy link
Contributor

tbu- commented Mar 10, 2015

Certainly, having another trait harms ergonomics.

I would like to disagree, having another trait doesn't make me write .into_iterator after Ranges I want to use as an iterator, it merely means that I can now choose between Pod, Copy, Clone, but that doesn't make my code longer.

@RalfJung
Copy link
Member

having another trait doesn't make me write .into_iterator after Ranges I want to use as an iterator

True. Though probably, many things currently taking an Iterator, should rather take an IntoIterator, then this would go away, wouldn't it?

it merely means that I can now choose between Pod, Copy, Clone, but that doesn't make my code longer.

I would argue that the additional complexity this introduces harms ergonomics as well, as it makes it harder to learn the language. (Maybe you used a more narrow definition of ergonomics than I did.) Explaining the difference between Pod and Copy can get pretty subtle. First I really liked the idea of introducing this distinction, but when I read (and agreed) that a struct full of Pod members can and should usually be derived Copy, that stuck me out as really strange. Essentially, this would mean a newtype of a Pod is Copy, which implicitly moves down in the hierarchy. That, somehow, doesn't sound right to me.

@japaric
Copy link
Member Author

japaric commented Mar 10, 2015

@Ericson2314

what are the coherence issues with the blanket impl you mention in the RFC?

Mainly overlapping impls, see rust-lang/rust#17884 (comment).

If that doesn't work, what about having Copy :< Pod :< Clone instead?

Yes, that's another option. I don't know if Pod can be an OIBIT in that case. OIBIT supertraits (over anything else than : MarkerTrait) are untested.

(Though it would be nice to enforce nobody can write an "interesting" clone on a PODtype.

Yes, that was the main idea - there is only one way to clone a Pod type, so the type system should derive it.

@Ericson2314
Copy link
Contributor

Ah ok. Perhaps in the short term lints can be used to prevent anything that would break if the blanket impls were added later (once negative bounds exist). #[deriving(Pod)] and #[deriving(Copy)] would then only temporarily need to derive multiple traits.

@japaric
Copy link
Member Author

japaric commented Mar 10, 2015

@nikomatsakis

I currently lean against using OIBIT for Pod. It has the advantage of (I think) making any changes backwards compatible

OIBIT Pod can silently change the semantics of some structs, which may be considered a backcompat hazard. For example:

// from rust-lang/rand - iOS specific code
struct OsRng {
    _dummy: (),
}

OsRng doesn't impl Drop or impl Copy, however once OIBIT Pod lands it would silently (no compiler error/warning) become a Pod type. This has to be fixed by opting out with impl !Pod for OsRng in the next release. However there will be crate version that has different semantics when compiled with e.g. rust 1.0 vs rust 1.1 (buggy Podness due to OIBIT).

It should be noted that these types that are not Copy and don't have destructors are fairly rare (at least in my experience).

@japaric
Copy link
Member Author

japaric commented Mar 10, 2015

OIBIT Pod can silently change the semantics of some structs

That being said, I'd prefer to do this before 1.0, because crate versions are short lived, so a buggy combination of crate version + (nightly) compiler version will be phased out quickly.

@japaric
Copy link
Member Author

japaric commented Mar 10, 2015

@RalfJung

True. Though probably, many things currently taking an Iterator, should rather take an IntoIterator

Yes, we have been relaxing some bounds from I: Iterator into I: IntoIterator where the iterator is a non-self argument taken by value.

then this would go away, wouldn't it?

The main pain point comes from things like 0..10.map(..) and 0..10.filter(..) and those methods come from IteratorExt, but I don't think it's possible to change impl IteratorExt for I where I: Iterator to impl IteratorExt for I where I: IntoIterator because IteratorExt has some methods that take &mut self and into_iter takes by value. (Unless we split IteratorExt in two traits...)

@RalfJung
Copy link
Member

Well, it would be more of an IntoIteratorExt, right ;-) ? If I see it correctly, both map and filter would work fine. But I see this has a significant tail of changes required to get the old ergonomics back. But then, I could just as well argue that some_vec.map() is something I should be able to write. I still think it's the "right thing" to do (and I have yet to see anybody reasoning against this), but it may not be feasibly anymore at this point in time. Fair enough, though it's a pity.

@RalfJung
Copy link
Member

RalfJung commented Mar 10, 2015 via email

@Gankra
Copy link
Contributor

Gankra commented Mar 10, 2015

I disagree that vec.map should work; IteratorExt has premium method names. map, filter, chain, cloned, zip, enumerate, skip, take, scan, inspect, by_ref, max, min... Making those methods inaccessible (or worse, traps) on anything that implements IntoIterator would be a nightmare.

@Ericson2314
Copy link
Contributor

@gankro is right. Persistent collections will need those method names. Looking at the original problem, Ranges could just implement Interator though.

@RalfJung
Copy link
Member

RalfJung commented Mar 11, 2015 via email

@Ericson2314
Copy link
Contributor

Oh sorry, I thought the proposed change of making the methods work on all IntoIterators was in response to Range already implementing IntoIterator and not Iterator, when in relativity none of that is true today.

@eddyb
Copy link
Member

eddyb commented Mar 12, 2015

@glaebhoerl Apologies for the late response, I've been meaning to get to this.
At least I came up with something new in the meanwhile, regarding the "no Iterator should implement Copy issue:

The problem I see there is that for loops will copy an Iterator, not that an Iterator is copyable.
Why do for loops even do that? Because of the simplistic desugaring that always defers to IntoIterator.

With that scheme, Iterator types still work in for loops, but only because of a blanket impl.
IMHO the old semantic of for loops (mutably borrowing the Iterator) is desireable and it happens to do away with this problem. I believe it wasn't kept because of the desugaring overhead.

Given IntoIterator, that semantic would have to be adapted to "mutably borrow if lvalue that implements Iterator, use IntoIterator otherwise".
Am I proposing redoing the desugaring for the n-th time and have it again down deeper in the compiler? Not really, that seems counter-productive at this stage.

Instead, we can either introduce an internal expression kind (just like ExprInlineAsm) or special-case some intrinsic/lang-item to handle converting the iterator/collection into an iterator.
It's an implementation detail and shouldn't hurt anyone - however, it would be a breaking-change as let r = 0..n; for i in r {...} will require r to be mutable again.

Alternatively, we could either make for loops move their iterator, even if it's normally Copy or lint uses of lvalues which have been copied into a for loop.


One other case I see Copy implementations missing (where the type would be Pod) is related to avoiding large copies.
That seems quite misguided, as it denies copies and it's not flexible enough to express anything useful for high-performance applications.
We don't have the type-level mechanisms for making, e.g. [u8; 1<<31], not Copy, and even if we did, it would be hardcoded.

If, instead, we used lints for this, here's what we could do:

  • pick between "don't care" and "no copies shall pass" for an entire crate/module/function/etc.
  • configure a threshold in either of:
    • bits
    • bytes
    • multiples of usize
    • cache lines (on the target CPU)
    • pages
    • average energy consumption (on the target CPU)
    • average time estimate for the copy in:
      • cycles
      • nanoseconds
        • etc.
  • optionally pretend usize is u64 to keep results consistent between architectures
  • lint copies where more than a certain percent of the size is padding
  • lint llvm.memcpy calls with constant size left around after optimizations
    • can catch expensive moves, too
    • more relevant from a performance perspective, especially when most of the copies are optimized away

@japaric
Copy link
Member Author

japaric commented Mar 12, 2015

@eddyb

Given IntoIterator, that semantic would have to be adapted to "mutably borrow if lvalue that implements Iterator, use IntoIterator otherwise".

I'll admit that having for loops take by value or by mutable reference depending on the context feels odd and can make code harder to read, though we may end up doing something similiar if we decide to do autoref on binary operations.

Alternatively, we could either make for loops move their iterator, even if it's normally Copy

This sounds like a reasonable alternative, but how would it be implemented?

or lint uses of lvalues which have been copied into a for loop.

I think this would be too noisy, it would throw a warning on all the for i in 0..10 { .. }s.

One other case I see Copy implementations missing (where the type would be Pod) is related to avoiding large copies.

I think that having a Pod/Copy distinction helps in this case. All these "large" stack types would be Pod (if we choose the OIBIT route), and the downstream users get to decide if they want to make the type implicitly copyable with some newtype:

struct Copyable<T>(T) where T: Pod;
impl<T> Copy for Copyable<T> {}
// also `impl Deref[Mut] for Copyable`

or if they want to use the move semantics. Your proposed lints plus profiling can help users choose between implicitly copyable or not.

@eddyb
Copy link
Member

eddyb commented Mar 12, 2015

@japaric why deny copies through the type-system when you can just turn the lint on? "Move semantics" do not prevent calling llvm.memcpy, just duplicating the value in a pathological case.

Marking a value as moved even though its type implements Copy can be done in borrowck (using any of the methods describe to special-case the creation of an Iterator from a collection or an existing Iterator).

And no, the lint would not warn 0..10, because that's not an lvalue, it's an rvalue.
{let it = 0..10; for i in it { .. }} would still not warn because there's no use of it after the for loop.

@Gankra
Copy link
Contributor

Gankra commented Mar 12, 2015

@japaric I disagree that Copy/Pod should be used to lint against moving large values. This seems like a completely orthogonal issue.

@eddyb Iterator is only one of the problems with the binary Copy/Clone situation. RNGs are another example of things that can often be bitwise copied safely, but will likely lead to incorrect behaviour if it is done implicitly.

I also disagree that this proposal complicates the Clone hierarchy. This completes and unifies it. Right now we have two unrelated traits: Copy and Clone. Although everything that is Copy is logically Clone with a particular impl, this is not enforced.

This creates a proper hierarchy:

  • Clone: Can be duplicated somehow.
  • Pod: Can be duplicated by copying the bits.
  • Copy: Can be duplicated by copying the bits, and never a problem to do implicitly (one interpretation would be that it is a stateless value).

99% of generic bounds should take Clone, but a few will want Pod. Copy is simply an ergonomic helper in concrete code.

@nikomatsakis
Copy link
Contributor

After having given this more thought (and some discussion) I am pretty strongly in favor of adding a lint. The idea would be that you can annotate a type to say it should not be implicitly copied. The lint would detect when a value is implicitly copied and then used again afterwards, and warn on the second use.

Advantages I see:

  1. Fewer choices. No need to explain the Pod/Copy distinction, which is not a deep semantic distinction but rather a question of how the type ought to be used (basically to avoid footguns).
  2. It is backwards compatible to add the annotation later to an existing type. This means that if I define a type and only later find that implicitly copying it is causing problems, I can add a warning.
  3. It's also backwards compatible to take the annotation away, if you find that the footgun is not so bad as you thought.
  4. It's easy to turn off the lint (add clone).

This seems to be pretty similar to the must_use lint, and I think that's worked out fairly well.

@huonw
Copy link
Member

huonw commented Mar 13, 2015

It seems to me that there's some edge-cases that make a lint hard, e.g. what happens when storing the Pod type inside a Copy type? (Especially a generic Copy type, like Option<SomeTypeThatShouldntBeCopied>.) Also,

struct Foo {
    bad_copy_field: SomeTypeThatShouldntBeCopied
    some_other_field: i32
}

{
    // ok, not the same field
    let foo = make();
    let a = foo.bad_copy_field;
    let b = foo.some_other_field;
}
{
    // ok, reinitialising
    let foo = make();
    let a = foo.bad_copy_field;
    foo = make(); // or foo.bad_copy_field = make2()
    let b = foo.bad_copy_field;
}

{
    // bad, using the same field twice
    let foo = new();
    let a = foo.bad_copy_field;
    let b = foo.bad_copy_field;
}
{
    // bad, some_method might use bad_copy_field
    let foo = new();
    let a = foo.bad_copy_field;
    foo.some_method();
}

I guess the lint is effectively a move checker that emits warnings, rather than errors.

(To be clear, despite that, I don't think the lint option is bad.)

@RalfJung
Copy link
Member

Right now we have two unrelated
traits: Copy and Clone. Although everything that is Copy is logically
Clone with a particular impl, this is not enforced.

Oh, that's indeed surprising. I would have expected a blanket "Clone"
implementation for all "Copy" types, that simply does memcpy.
Does this mean that for Niko's proposal, to be able to turn off the link
using ".clone()", one has to remember to implement Clone besides Copy?

@glaebhoerl
Copy link
Contributor

@gankro

one interpretation would be that it is a stateless value

Is there really any actual distinction here? Even with a simple i32, it is stateful if you mutate it and use &mut, and stateless if you don't and copy it or use &. Are the RNGs and iterators any different in this respect? I think the difference is entirely in the intent, which is only in the developer's head, and may be different from case to case.

@eddyb
Copy link
Member

eddyb commented Mar 13, 2015

@glaebhoerl I full-heartedly agree, which is why I'd rather error/warn on accidental copies in for loops specifically, instead of linting all copies of iterators.
RNGs don't have an equivalent specific construct, however, I do believe copying RNGs is much rarer than copying iterators, so warning on copies of any type that implements Rng doesn't seem excessive to me.

OTOH, implementing Copy (or not opting out of it, if we go for an OIBIT Send-like model) lets you use RNGs in Cells.
I just checked: XorShiftRng is just [u32; 4], definitely something I don't want to wrap in a RefCell if I can help it.

@nikomatsakis
Copy link
Contributor

Consensus seems to be that the lint option is better (I've also spoken with the author, @japaric, about this). There I am going to close this RFC.

@Gankra
Copy link
Contributor

Gankra commented Mar 20, 2015

@nikomatsakis even Copy: Clone is out of the question?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet