5 ms·
As a Rust beginner I am not quite sure why .min() is supposed to take ownership and change the sequence. I would assume it does nothing with the sequence except
by bayesian_horse 8y ago
As a Rust beginner I am not quite sure why .min() is supposed to take ownership and change the sequence. I would assume it does nothing with the sequence except read it.
- kilburn 8y agoSequences can be generated at runtime, and it may even be impossible to "regenerate" them (think for instance reading bytes from the network). Therefore, just reading that sequence changes (consumes) it.
- MaulingMonkey 8y agoAnd of course for things that can be iterated over multiple times, it's likely possible that you can (re)generate the iterator. E.g. this compiles fine: fn main() { let squares : Vec<u32> = (0..10).map(|x| x * x).collect(); println!("{:?}", squares.iter().min()); // Some(0) println!("{:?}", squares.iter().max()); // Some(81) } Edit: Or a smaller change to the original, you can add a single .clone(): fn main() { let squares = (0..10).map(|x| x * x); println!("{:?}", squares.clone().min()); println!("{:?}", squares.max()); } This clones the std::iter::Map<std::ops::Range<i32>>. Unlike the vec version, this will re-evaluate the |x|x*x sequence when .max() is called. There's no technical reason Map<Range<i32>> couldn't be made to implement the Copy trait, which would make the extra .clone() here unnecessary - but Rust's standard library has chosen to force you to make an explicit choice instead.
- lostmsu 8y agoCuriously, C# provides the behavior one would expect. It does so by distinguishing IEnumerable<T> [1], and IEnumerator<T> [2]. The first would be the result of the map call (Select in C#'s LINQ), representing a sequence, that could be generated, and the later is the one, that actually generates it one by one, and has to be consumed. I wonder why Rust did not take C#'s approach. [1] https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1 https://docs.microsoft.com/en-us/dotnet/api/system.collectio... [2] https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerator-1 https://docs.microsoft.com/en-us/dotnet/api/system.collectio...
- kibwen 8y agoCan you be more specific about what behavior from the Rust code you find unexpected, in comparison with C#?
- lostmsu 8y agoExactly the consumption part. Perhaps I don't understand Rust library, and there's another stuff to do it right. In C# Enumerable.Range and .Select (e.g. .map) internally create an immutable instance, that implements IEnumerable<T>, that describes what will have to be returned. Then .Min internally takes IEnumerable<T>, calls its .GetEnumerator, that actually returns something, that can be consumed once and has a mutable state, and finds the minimum by actually consuming that iterator (all internally).
- MaulingMonkey 8y agoI'd say the distinction is that Rust's .min() is implemented on the equivalent of IEnumerator<T>, Iterator - instead of the equivalent of IEnumerable<T>, IntoIterator. This makes it slightly more flexible, since you can always get the former from the latter by calling GetEnumerator(), but not vicea versa (without filling a container with it first or something.) If C# also implemented Min on IEnumerator<T>: var enumerator = collection.GetEnumerator(); var min = enumerator.Min(); var max = enumerator.Max(); // Runtime bug: Min() already the entire enumerator! InvalidOperationException? There's basically nothing useful you can do with 'enumerator' after the first call to this hypothetical Min. Rust's stdlib tries to catch this bug by having min take ownership of/consume 'enumerator', instead of merely borrowing it, so you can't use it again: let iter = collection.iter(); let min = iter.min(); let max = iter.max(); // Compile time error: iter used after move In C#, basically everything that can be is an IEnumerable<T>, and the stdlib is built around that fact. Does System.IO.File.ReadLines hit disk every time you re-enumerate it? Or does it store to a container? Many of the stream based APIs have a .ReadLine() method but no .ReadLines() method, presumably because they'd have to allocate a container to store the result in case enumerable be re-evaluated multiple times, and that seems bad if you don't need it? Rust seems to lean a lot more heavily on single-shot iterators. let lines = stdin.lock().lines(); for line in lines { ... } for line in lines { ... } // Compile time error: lines moved. let lines = BufReader::new(File::open("input.tsv")?).lines(); for line in lines { ... } for line in lines { ... } // Compile time error: lines moved. "Does lines() hit disk/stdin every time you re-enumerate it?" is a trick question: You can't re-enumerate it. You can resolve these errors by being explicit about storing the input in a container and iterating that multiple times, or by re-opening the input file, or by resetting the File position and constructing a new BufReader, depending on which behavior you wanted for re-enumeration.
- slavik81 8y agoThat seems strange to me. If calling max() changed the sequence, I would expect it would have to be declared as mutable. I struggled with this when writing my first rust program. I was very surprised by the behaviour.
- danieldk 8y agoIf calling max() changed the sequence, max does not change a sequence, it consumes the iterator. I would expect it would have to be declared as mutable max takes self (and not &self), so the ownership of the iterator is moved to the max method. The new owner can decide to bind the value mutably. This is always the case when a value is moved and changes ownership, e.g.: https://play.rust-lang.org/?gist=50e6fe86e530baeabda1a7cca3c785ab&version=stable&mode=debug&edition=2015 https://play.rust-lang.org/?gist=50e6fe86e530baeabda1a7cca3c...
- slavik81 8y agoI suppose designing it that way still prevents you from doing anything wrong, but you need quite a lot of knowledge of the system to understand what it's doing. I'll get there one day, but it's hard to go back to having no idea what my compiler is trying to do.
- atoav 8y agoBecause it runs on an iterator. And anything that runs on an iterator consumes it. If you for example run // Make a iterator let test_iterator = vec![1, 2, 3].into_iter(); // count() iterates over the elements and consumes them let count = test_iterator.count(); // because the values have been consumed, this is not possible for element in test_iterator{ println!("{}", element); } See: https://play.rust-lang.org/?gist=750dffd641ac237690defbf86dabc935&version=stable&mode=debug&edition=2015 https://play.rust-lang.org/?gist=750dffd641ac237690defbf86da... The count method will consume every element, and return the length. This is because iterators are meant to be consumed when iterated. This is maybe inconvenient, but necessary because of the generic nature of the iterator trait: for some things that implement the iterator trait it is just not possible to get the max(), min() or count without consuming the iterator. So this would work: // Make a iterator let test = vec![1, 2, 3]; // Create an Iterator and consume it with count() let count = test.iter().count(); println!("Count: {}\n", count); // Create another Iterator and loop over it (consume it in each row) for element in test.iter(){ println!("{}", element); } // Create another Iterator and consume it with min() let min = test.iter().min(); println!("\nMin: {}", min.unwrap()); Try here: https://play.rust-lang.org/?gist=498ffd9afa163909745d8537e97c7542&version=stable&mode=debug&edition=2015 https://play.rust-lang.org/?gist=498ffd9afa163909745d8537e97... That means, there was probably the choice: try keeping it generic and consistent for all things that implement iterator or making these methods something that works fine with basic types but not at all with anything more complex. So: Iterators don't know any value until they are consumed. That means iterators will be consumed most of the time (there are exceptions. Also see: https://doc.rust-lang.org/std/iter/index.html#implementing-iterator https://doc.rust-lang.org/std/iter/index.html#implementing-i... And: https://doc.rust-lang.org/std/iter/trait.Iterator.html https://doc.rust-lang.org/std/iter/trait.Iterator.html