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

Revert "remove Copy impls from remaining iterators" #21846

Closed
wants to merge 1 commit into from

Conversation

Projects
None yet
9 participants
@tbu-
Copy link
Contributor

tbu- commented Feb 1, 2015

This reverts commit c3841b9.

The commit made it impossible to copy the pretty fundamental data type Range and RangeFrom, which e.g. means that they can't be used in otherwise copyable structures anymore (or Cells for that matter), where the reason for removal was that it can also be used as an iterator.

CC @japaric
#21809

@rust-highfive

This comment has been minimized.

Copy link
Collaborator

rust-highfive commented Feb 1, 2015

r? @alexcrichton

(rust_highfive has picked a reviewer for you, use r? to override)

@Diggsey

This comment has been minimized.

Copy link
Contributor

Diggsey commented Feb 2, 2015

IMO ranges should implement "IntoIterator" rather than being iterators themselves, it doesn't make too much sense and it's caused a few other conflicts as well as this.

For example, currently "RangeTo" is not iterable at all, because there's currently no such thing as a "ReverseIterator" trait, and it only makes sense to iterate a "RangeTo" in reverse. If ranges are not themselves iterators, then "RangeTo" can just have a method to return a reverse iterator over that range, without breaking the symmetry with "RangeFrom" which has a similar method as part of its implementation of "IntoIterator", but for a forward iterator.

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Feb 2, 2015

CC @aturon

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Feb 4, 2015

I quite like @Diggsey's solution, personally. @tbu- would you be interested in going in that direction?

@tbu-

This comment has been minimized.

Copy link
Contributor Author

tbu- commented Feb 4, 2015

@aturon I can do this, but it will break much code like (0..len).map(|_| 'a').collect(), making the need for Vec::from_elem even bigger. Doing it together with re-adding the respective from_elem functions might be viable.

@tbu-

This comment has been minimized.

Copy link
Contributor Author

tbu- commented Feb 7, 2015

@aturon Any news on this? Range still can't be embedded into structures...

@japaric

This comment has been minimized.

Copy link
Member

japaric commented Feb 7, 2015

Range still can't be embedded into structures...

Is that an insurmountable problem? Range fields are public, you can repack them into a copyable tuple (or equivalent) at the constructor/function boundary.

#[derive(Copy)] struct Struct { .. }
fn constructor(r: Range<i32>) -> Struct {
    Struct { start: r.start, end: r.end, .. }
}

Then you can reconstruct the iterable range from the copies: struct.start..struct.end (yes, verbose but not impossible)

@tbu- Could you elaborate on what functionality can't be achieved because Range is not Copy? I do reckon it can be a little unergonomic (I'd still take range.clone().map(..) over (a..b).into_iter().map(..) any time of the day though).

@tbu-

This comment has been minimized.

Copy link
Contributor Author

tbu- commented Feb 7, 2015

@japaric I can't use them as public fields in my structs, constructors means you cna't construct a struct at compile-time, setters mean that I can't have per-field mutable borrows anymore.

Things that are "a little unergonomic" have really bad impact.

@tbu-

This comment has been minimized.

Copy link
Contributor Author

tbu- commented Feb 7, 2015

@japaric There's no reason Range must be non-Copy and by making it so you make it impossible to re-use language-features in user code, because unlike other traits you can't even override Copy although it is obvious that Range is trivially copyable. You're changing Range to be no-Copy because it's an iterator, but there's more things to Range than just that it's an iterator, it's also a struct that has language-level sugar for construction.

@japaric

This comment has been minimized.

Copy link
Member

japaric commented Feb 7, 2015

I can't use them as public fields in my structs

Of course, you don't have to, you can keep the fields private.

constructors means you cna't construct a struct at compile-time

Yes, but you just said above that you can't make the range public, so you need a constructor. (?)

setters mean that I can't have per-field mutable borrows anymore

Sorry, I don't quite understand this statement, you mean a getter that freezes the struct?

There's no reason Range must be non-Copy

The original problem was #18045, which perhaps could have been prevented using a lint that triggers when an iterator is implicitly copied, but the core-devs opted for making iterators not implement Copy.

you make it impossible to re-use language-features in user code

(I think impossible is an overstatement)

that Range is trivially copyable

Range is a trivial memcpy (which is what clone does), yes, but that doesn't mean that it should be implicitly copyable. The current Copy trait conflates both concepts (memcpyable and implictly copyable). There was some idea floating around about splitting the trait in two: Pod (plain old data/memcpyable data), and Copy (implicitly copyable/cloneable data), but no one has written an RFC on the subject (perhaps I should before it's too late?). Splitting the traits may help in this case, but I don't know what exactly @tbu- is intending to do.

@tbu- I'd still like to see what use case you have problems with.

@tbu-

This comment has been minimized.

Copy link
Contributor Author

tbu- commented Feb 7, 2015

@japaric It may indeed be the problem that the two things are conflated – not implementing Copy locks out compound data types from implementing Copy themselves.

The core-devs might have missed that they were making Range non-Copy-able with their decision.

I'm not sure if you're mocking me with your line-by-line reply, but what I wanted to say with the different options is that none of them are suitable: If I make the fields public, the sugar isn't there, if I use a constructor it can't be constructed statically, if I use setters and getters I always need complete ownership of the struct before I can set a single member.

@tbu-

This comment has been minimized.

Copy link
Contributor Author

tbu- commented Feb 7, 2015

@japaric To see why sugar is important, let me suggest to remove the Iterator implementation of Range: You can still do the same things, surely it's a little more verbose, but what kind of functionality cannot be achieved with that?

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Feb 7, 2015

@tbu-

@aturon Any news on this? Range still can't be embedded into structures...

Sorry for the delay. Let me respond to your earlier comment.

@aturon I can do this, but it will break much code like (0..len).map(|_| 'a').collect(), making the need for Vec::from_elem even bigger. Doing it together with re-adding the respective from_elem functions might be viable.

There have been a lot of requests to reintroduce from_elem, which was dropped only to slim down the API surface. So I'd be happy to review a PR re-introducing it, along with the other changes we've talked about here. Feel free to r? me.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Feb 7, 2015

@japaric

There was some idea floating around about splitting the trait in two: Pod (plain old data/memcpyable data), and Copy (implicitly copyable/cloneable data), but no one has written an RFC on the subject (perhaps I should before it's too late?).

I've talked about this with @nikomatsakis and I think we definitely want to do it at some point, but I believe it can be done backwards-compatibly.

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Feb 7, 2015

Being able to treat (a..b) as an iterator is a really nice pattern outside of from_elem. (a..b).iter() isn't exactly a tragedy, though.

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Feb 7, 2015

A nice benefit of making ranges only IntoIter is that it resolves the need for a hypothetical RangeInclusive to include a has_yielded_inclusive boolean.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Feb 7, 2015

@Gankro

Being able to treat (a..b) as an iterator is a really nice pattern outside of from_elem. (a..b).iter() isn't exactly a tragedy, though.

Can you give some examples to clarify this? Any code that currently takes an Iterator should be changed to take an IntoIterator instead.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Feb 7, 2015

@Gankro

Being able to treat (a..b) as an iterator is a really nice pattern outside of from_elem. (a..b).iter() isn't exactly a tragedy, though.

Can you give some examples to clarify this? Any code that currently takes an Iterator should be changed to take an IntoIterator instead.

Ah, hm, I bet you mean things like (0..10).map(f).collect()?

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Feb 7, 2015

To be clear, the non-Copy for iterators is essentially a guideline to help avoid confusing bugs -- not a hard rule. If it turns out that it's needed for ranges for the best ergonomics, that may be a trade worth making.

cc @alexcrichton @huonw

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Feb 7, 2015

Yeah (a..b).mess().of().adaptors() is pretty pleasant in my opinion.

@japaric

This comment has been minimized.

Copy link
Member

japaric commented Feb 7, 2015

@tbu-

I'm not sure if you're mocking me with your line-by-line reply

Certainly no. I apologize if it seem that way. I was trying to understand your use case but failed to do so.

To see why sugar is important, let me suggest to remove the Iterator implementation of Range: You can still do the same things, surely it's a little more verbose, but what kind of functionality cannot be achieved with that?

Sugar is important, but there is trade off between ergonomics and a potential source of bugs. Removing Iterator from Range will generate way more fallout (not just the from_elem replacement, but also stuff like for _ in (a..b).map(..)) than removing Copy from Range.

@aturon

I've talked about this with @nikomatsakis and I think we definitely want to do it at some point, but I believe it can be done backwards-compatibly.

I was mocking a design, and it was backward incompatible, but if you have explored the space and think it can implement in bakcward compatible way, then I trust your decision of postponing it for after 1.0.

If it turns out that it's needed for ranges for the best ergonomics, that may be a trade worth making.

I'd be in favor of implementing Copy for Range, and adding some lint that either:

  • warns when a struct derives both Iterator, and Copy (I don't hink this is enough, the lint should warn at "call site" rather than at "impl site")
  • warns when an iterator is implictly copied (this feels too noisy for Range)
  • warns when the copy of an iterator is advanced, and then the original is used, basically catch this for _ in it { if cond { break } } do_something_with(it.next())); (this feels hard to implement, but I don't have experience writing lints).
@Diggsey

This comment has been minimized.

Copy link
Contributor

Diggsey commented Feb 7, 2015

warns when the copy of an iterator is advanced, and then the original is used, basically catch this for _ in it { if cond { break } } do_something_with(it.next()));

This example makes me feel especially strongly that ranges should be IntoIterator rather than Iterator. If I iterate through a range using a for loop, it's extremely counterintuitive to me that the range is modified in the process.

While it's slightly more work to type ".iter()" to use it as an iterator:

  1. It's not that big a deal
  2. Commonly used methods of the type "range.iter().method()" can always be implemented directly for "IntoIterator" allowing "range.method()" to continue to work. This comes with the added benefit that those methods will work for all iterable types, not just ranges.
@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Feb 11, 2015

The conflict between "leaving out Copy to try to catch bugs like #18045" and "doing so (leaving out Copy) makes such types unusable with Cell" was actually brought up during the discussion on #18045.

One possible way to resolve that particular conflict could be the route suggested by @huonw here:
#20813 (comment) which was to decouple the notion of "safe for use with cell" (aka "memcpy-is-safe", aka Pod) from "remove move-by-default" (aka Copy). Then we would add in trait Copy : Pod { }, so that there is still a relationship between the two, (but merely a unidirectional implication).

Though as @huonw said at the time, the complexity of that solution may not pay for itself.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Feb 11, 2015

@pnkfelix I think in the long run we will definitely want to go in this direction, but @nikomatsakis and I believe that we can do so backwards-compatibly.

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Feb 11, 2015

@aturon oh sorry, I see you wrote the same thing up above; sorry to make you repeat yourself!

@tbu-

This comment has been minimized.

Copy link
Contributor Author

tbu- commented Feb 13, 2015

@aturon So I made an IntoIterator implementation for Range – but the problem is that type inference falls down for that. For example this code snippet

fn a(x: u32) {}

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

fails with the error message "expected u32, found i32", so I think that this isn't a viable option. I think the best option (and the only one I can think of), is just to implement Copy for Range again, and continue to implement Iterator, like before.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Mar 10, 2015

@tbu-

This comment has been minimized.

Copy link
Contributor Author

tbu- commented Apr 14, 2015

@aturon Now the decision in the RFC was to add a lint some day – so could we merge this Copy implementation?

Manishearth added a commit to Manishearth/rust that referenced this pull request Apr 17, 2015

Rollup merge of rust-lang#21846 - tbu-:pr_revert_uncopyable_range, r=
 This reverts commit c3841b9.

The commit made it impossible to copy the pretty fundamental data type `Range` and `RangeFrom`, which e.g. means that they can't be used in otherwise copyable structures anymore (or `Cell`s for that matter), where the reason for removal was that it can *also be used as an iterator*.

CC @japaric
rust-lang#21809

Manishearth added a commit to Manishearth/rust that referenced this pull request Apr 17, 2015

Rollup merge of rust-lang#21846 - tbu-:pr_revert_uncopyable_range, r=
 This reverts commit c3841b9.

The commit made it impossible to copy the pretty fundamental data type `Range` and `RangeFrom`, which e.g. means that they can't be used in otherwise copyable structures anymore (or `Cell`s for that matter), where the reason for removal was that it can *also be used as an iterator*.

CC @japaric
rust-lang#21809
@bors

This comment has been minimized.

Copy link
Contributor

bors commented May 10, 2015

☔️ The latest upstream changes (presumably #25262) made this pull request unmergeable. Please resolve the merge conflicts.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented May 19, 2015

@tbu- Sorry, this one got buried and I missed it.

In the Pod/Copy decision we determined that a lint was better than adding a further split to the marker traits, but I don't think there was an explicit decision about cases like this prior to the lint landing. I personally don't have a strong opinion here, though. I will ask the library subteam!

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Jun 9, 2015

As per discussion within the libs team on IRC (https://botbot.me/mozilla/rust-libs/2015-05-19/?msg=39609682&page=1), we're not going to take this at the moment. If in the future we develop a lint to make it more feasible to make this Copy without being a footgun, we can reconsider.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.