On 11.08.2013 12:01, Armin Ronacher wrote:
The way "yield return" works in C# is that it rewrites the code into a
state machine behind the scenes. It essentially generates a helper
class that encapsulates all the state.
In Rust that's much harder to do due to the type system. Imagine you
are doing a yield from a generic hash map. The code that does the
rewriting would have to place the hash map itself on the helper struct
that holds the state. Which means that the person writing the
generator would have to put that into the return value.
I think transforming the yielding function into a state machine, like
done in C#, will be the way to go for Rust too.
Rust's type system makes this a bit more complicated than in C#.
However, the necessary code transformation has many similarities with
supporting closures:
* For a closure, the compiler generates a hidden environment struct,
containing the captured variables, which is then implicitly passed to
the closure function.
* For yield, the compiler also generates a hidden struct, implementing
the state machine logic, but also 'capturing' the arguments and local
parameters of the yielding function.
It is really rather similar as far as the type system is concerned. If
we can define sound semantics for closures, we should also be able to
define sound semantics for yielding functions.
As for having to put the hashmap into the return value, I don't think
this is necessary because implementation details of the function are
hidden behind the std::Iterator trait.
I imagine, the compiler would do a desugaring like the following:
// Original function
fn yield_some(xs: &'a HashMap<int, float>, a: int, b: int) ->
Iterator<float> {
yield return xs.get(a);
yield return xs.get(b);
}
// Desugared version
fn yield_some(xs: &'a HashMap<int, float>, a: int, b: int) ->
yield_some_iterator<'a> {
return yield_some_iterator{
state: 0,
xs: xs,
a: a,
b: b,
};
}
struct yield_some_iterator<'self> {
priv state: uint,
priv xs: &'self HashMap<int, float>,
priv a: int,
priv b: int
}
impl std::iterator::Iterator<float> for yield_some_iterator {
fn next(&self) -> Option<float> {
match self.state {
0 => {
state = 1;
Some(self.xs.get(self.a))
}
1 => {
state = 2;
Some(self.xs.get(self.b))
}
2 => None
}
}
}
As you can see, the compiler substitutes a concrete iterator type. But
this iterator type can only ever be accessed through the
std::iterator::Iterator interface, which only exposes the return value
of the next() method. The internal state of the iterator (such as the
hash map) does not have to be considered by the user. (The lifetimes of
the yielding function's arguments can, however, influence the lifetime
of the resulting iterator, as is the case with closures and captured
variables).
I currently have a really hard time thinking about how the c# trick
would work :-(
Maybe you do now :)
Aside from this some random notes from Python:
- generators go in both directions in Python which caused problems
until Python 3.3 where "yield from" (your "yield ..") was introduced
that expands into a monstrosity that forwards generators into both
directions.
Can you elaborate on what you mean by "both directions"?
- instead of using "fn" like "def" in Python I would prefer if it was
an explicit "yield fn" that indicates that the function generates an
iterator. The fact that Python reuses "def" is a source of lots of
bugs and confusion.
I think Rust has an advantage over Python here, in that for every
function the return type is explicitly declared. So, if a function
returns an Iterator<T> (or better), one does not have to care about
whether the function is implemented via 'yield' or if it returns a
handwritten iterator. So I think "yield fn" would be redundant here. The
type checker won't let through anything confused.
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev