News for hackers. Stuff that matters.
Re:Rust Ownership Explained with Python by bayesian_horse on Saturday September 29, 2018 @08:25PM (#18103816)
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.
Re:Rust Ownership Explained with Python by kilburn on Saturday September 29, 2018 @08:31PM (#18103831)
Sequences 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.

Re:Rust Ownership Explained with Python by MaulingMonkey on Saturday September 29, 2018 @09:32PM (#18104006)
And 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.

Re:Rust Ownership Explained with Python by lostmsu on Saturday September 29, 2018 @11:28PM (#18104305)
Curiously, 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.collectio...

[2] https://docs.microsoft.com/en-us/dotnet/api/system.collectio...

Re:Rust Ownership Explained with Python by marcianx on Sunday September 30, 2018 @12:33AM (#18104470)
Rust does have these abstractions. The corresponding rust traits are `IntoIterator<Item=T>` and `Iterator<Item=T>`. For example:

  - Vec<T> implements IntoIterator<Item=T>
  - &Vec<T> implements IntoIterator<Item=&T>
  - &mut Vec<T> implements IntoIterator<Item=&mut T>
and consequently,

  - vec.into_iter() implements Iterator<Item=T>
  - (&vec).into_iter() (or vec.iter()) implements Iterator<Item=&T>
  - (&mut vec).into_iter() (or vec.iter_mut()) implements Iterator<Item=&mut T>
Re:Rust Ownership Explained with Python by lostmsu on Sunday September 30, 2018 @01:10AM (#18104570)
Good, but how do you create something, that is IntoIterator, which holds an immutable description of how to generate members to iterate over? And I mean some potentially infinite sequence.
Re:Rust Ownership Explained with Python by heinrich5991 on Sunday September 30, 2018 @02:16AM (#18104781)
Re:Rust Ownership Explained with Python by lostmsu on Sunday September 30, 2018 @01:33PM (#18107195)
Can you .map it?
Re:Rust Ownership Explained with Python by Darsstar on Sunday September 30, 2018 @04:50PM (#18108344)
Re:Rust Ownership Explained with Python by kibwen on Sunday September 30, 2018 @12:07AM (#18104404)
Can you be more specific about what behavior from the Rust code you find unexpected, in comparison with C#?
Re:Rust Ownership Explained with Python by lostmsu on Sunday September 30, 2018 @12:57AM (#18104537)
Exactly 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).

Re:Rust Ownership Explained with Python by MaulingMonkey on Sunday September 30, 2018 @07:01PM (#18108944)
I'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.

Re:Rust Ownership Explained with Python by CryZe on Sunday September 30, 2018 @01:39AM (#18104633)
I think what's unexpected is that (0..10).map(|x| x * x) doesn't implement Copy. I think that's something that could be implemented as Copy closures are a thing now (or maybe soon). Although wait, the problem is more that (0..10) itself isn't Copy, now that I think about it.
Re:Rust Ownership Explained with Python by slavik81 on Sunday September 30, 2018 @12:18AM (#18104427)
That 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.

Re:Rust Ownership Explained with Python by danieldk on Sunday September 30, 2018 @02:42AM (#18104858)
If 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=50e6fe86e530baeabda1a7cca3c...

Re:Rust Ownership Explained with Python by slavik81 on Sunday September 30, 2018 @01:11PM (#18107074)
I 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.

Re:Rust Ownership Explained with Python by atoav on Sunday September 30, 2018 @06:46AM (#18105607)
Because 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=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=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-i... And: https://doc.rust-lang.org/std/iter/trait.Iterator.html