Yes, you read the title right! I was to add a new explicit way to apply an implicit conversion on multiple branches.
EDIT for future readers. This post and the following discussions touch 3 very related by different topics:
- allowing more unification in the branches of
if
/match
/loop
/for
/while
. - anonymous enum with named variant like
ParseIntError | ParseFloatError
- compiler-generated anonymous enum that implement a trait like
impl Iterator<Item=Foo>
I certain cases, we need to use .into()
many times.
// we assume that the `From` and `Into` traits are implemented
let output: Frob = match something {
Foo(a) => foo(a).into(),
Bar(b) => bar(b).into(),
Baz(c) => baz(c).into(),
};
It would be obviously more concise to have implicit conversion, but explicitness is an important part of Rust. Unfortunately, we can't use the From
trait instead of Into
since the arms don't match:
// this doesn't compiles
let output = Frob::From(match something {
Foo(a) => foo(a),
Bar(b) => bar(b), // incompatible type with `foo(a)`
Baz(c) => baz(c), // incompatible type with `foo(a)`
});
I suggest the introduction of a new keyword that would takes all branches or the right-hand side, and apply .into()
to them. This convertion logic is very similar to what the ?
operator is doing with the Err
.
let output: Frob = become match something {
Foo(a) => foo(a), // implicit `.into()`
Bar(b) => bar(b),
Baz(c) => baz(c),
};
Lets take a more realistic example: combining two iterators that output the same type.
enum EitherIterator<It1: Iterator, It2: Iterator> {
It1(It1),
It2(It2),
}
impl<It1, It2> Iterator for EitherIterator<It1, It2>
where
It1: Iterator,
It2: Iterator,
It2::Item: Into<It1::Item>,
{
type Item = It1::Item;
fn next(&mut self) -> Option<Self::Item> {
match self {
EitherIterator::It1(it) => it.next(),
EitherIterator::It2(it) => {
it.next().map(|item| item.into())
},
}
}
}
fn main() {
let v: [usize; 4] = [1, 2, 3, 4];
let iter: EitherIterator<_,_> = if rand::random() {
EitherIterator::It1(v.iter().copied())
} else {
EitherIterator::It2(v.iter().rev().map(|x| x*3))
};
for it in iter {
print!("{} ", it);
}
}
More general version where the Output can be user-defined
enum EitherIterator<It1, It2, Out>
where
It1: Iterator,
It2: Iterator,
Out: From<It1::Item>,
Out: From<It2::Item>,
{
It1(It1),
It2(It2),
Marker(std::marker::PhantomData<Out>),
}
impl<It1, It2, Out> Iterator for EitherIterator<It1, It2, Out>
where
It1: Iterator,
It2: Iterator,
Out: From<It1::Item>,
Out: From<It2::Item>,
{
type Item = Out;
fn next(&mut self) -> Option<Out> {
match self {
EitherIterator::It1(it) => it.next().map(|item| item.into()),
EitherIterator::It2(it) => it.next().map(|item| item.into()),
EitherIterator::Marker(_) => panic!(),
}
}
}
fn main() {
let v: [i16; 4] = [1, 2, 3, 4];
let iter: EitherIterator<_,_,f32> /* impl Iterator<Item=f32> */ = if rand::random() {
EitherIterator::It1(v.iter().copied()) // Iterator<Item=u16>
} else {
EitherIterator::It2(v.iter().rev().map(|x| (*x as i16) * -1)) // Iterator<Item=i16>
};
for it in iter {
print!("{} ", it);
}
}
This new become
keyword would simplify the call site and improve readability:
// EitherIterator is implemented the same way
fn main() {
let v: [usize; 4] = [1, 2, 3, 4];
let iter: EitherIterator<_,_> = become if rand::random() {
v.iter().copied() // no more boilerplate here
} else {
v.iter().rev().map(|x| x*3) // nor there
};
for it in iter {
print!("{} ", it);
}
}
As such, this feature is useful, but probably not enough to hold its weight. However, if we combine it with impl Trait type ascription, this can become extremely powerful: the whole EitherIterator
enum be compiler generated. EDIT: This obviously need compiler support to be able to generate such kind of anonymous enum that implement a given trait.
// No need to write EitherIterator, the compiler could do it for us
fn main() {
let v: [usize; 4] = [1, 2, 3, 4];
let iter: impl Iterator<Item=usize> = become if rand::random() {
v.iter().copied()
} else {
v.iter().rev().map(|x| x*3)
};
for it in iter {
print!("{} ", it);
}
}
Another example:
fn forward_or_reverse<T>(v: &Vec<T>, forward: bool) -> impl Iterator<Item=&T> {
become if forward {
v.iter()
} else {
v.iter().rev()
}
}