Suppose f() and g() return Option<T>.
if let (Some(x), Some(y)) = (f(), g()) {
h();
}
If f() returns None, will g() be evaluated? Is evaluation guaranteed or prohibited by the spec?
It does not shortcut. To pattern-match against the pair, the pair must be fully constructed, which means both f and g have been called. There is no lazy evaluation where the pattern match could happen before the values are calculated.
(By the way, it's easy to try out, and the Rust compiler pretty much is the spec right now.)
Related
In the vec! macro implementation there is this rule:
($($x:expr),+ $(,)?) => (
$crate::__rust_force_expr!(<[_]>::into_vec(box [$($x),+]))
);
What exactly is that <[_]> in it?
Breaking down the specific parts of the syntax:
<T>::f is the syntax to explicitly call f associated with a type T. Usually just T::f is enough, but pedantically, :: requires a path, which is why it is used here since [_] is not. The <...> allows any type to be used as a path. See Why do I need angle brackets in <$a> when implementing macro based on type?
[T] is the type denoting a slice of type T.
_ used as a type is a placeholder or "wildcard". It is not a type itself, but serves to indicate that the type should be inferred. See What does it mean to instantiate a Rust generic with an underscore?
Let's go step by step to see how <[_]>::into_vec(box [$($x),+]) produces a Vec:
[$($x),+] expands to an array of input elements: [1, 2, 3]
box ... puts that into a Box. box expressions are nightly-only syntax sugar for Box::new: box 5 is syntax sugar for Box::new(5) (actually it's the other way around: internally Box::new uses box, which is implemented in the compiler)
<[_]>::into_vec(...) calls the to_vec method on a slice containing elements that have an inferred type ([_]). Wrapping the [_] in angled brackets is needed for syntactic reasons to call an method on a slice type. And into_vec is a function that takes a boxed slice and produces a Vec:
pub fn into_vec<A: Allocator>(self: Box<Self, A>) -> Vec<T, A> {
// ...
}
This could be done in many simpler ways, but this code was fine-tuned to improve the performance of vec!. For instance, since the size of the Vec can be known in an advance, into_vec doesn't cause the Vec to be reallocated during its construction.
Rust beginner here. Why does this code work??
fn make_string() -> String {
let x = String::from("world");
x
}
fn main() {
let s = make_string();
}
The way I understand the rules, when the closing bracket of make_string is encountered, the value of x should be dropped, since x is the owner and goes out of scope. You could argue that ownership is transferred, but that seems to happen after x goes out of scope.
Is this a special case for functions, or am I fundamentally misunderstanding the rules?
When you return a value from a function, you move the value, essentially transferring ownership from that function to the parent function, which has a larger scope. Since the scope is now larger, the value won't be dropped.
See:
Rust by Example: Ownership and moves
The Rust Programming Language: What is Ownership?
Tuple elements may have side-effects, and some of them may depend on others. Consider this program:
fn main() {
let mut v = vec![1, 2];
match (v.pop(), v.pop()) {
(Some(z), Some(y)) => println!("y = {}, z = {}", y, z),
_ => unreachable!(),
}
}
Does it output y = 1, z = 2 or y = 2, z = 1? A few rounds on the Rust Playground suggests the former on stable 1.32.0, but maybe it would change if I ran it more times, recompiled the compiler, changed compiler versions, etc.
Is there a documented commitment or at least intention to maintain a particular order of evaluation for tuples (e.g. depth-first and left-to-right)?
Yes, the order of evaluation for tuples is guaranteed to be left-to-right (which also implies depth-first, as the value must be fully constructed).
Unfortunately, this is never stated explicitly anywhere that I can find, but can be inferred from Rust's strong backwards compatibility guarantees. Making a change to evaluation order would likely introduce far too much breakage to ever be seriously considered.
I'd also expect that the optimizer is allowed to make changes when safe to do so. For example, if the expressions in the tuple have no side effects, then reordering them is invisible to the user.
See also:
Rust tuple: evaluation order: left -> right?
The Rust Reference: Expressions
In this snippet from Hyper's example, there's a bit of code that I've annotated with types that compiles successfully:
.map_err(|x: std::io::Error| -> hyper::Error {
::std::convert::From::<std::io::Error>::from(x)
})
The type definition of From::from() seems to be fn from(T) -> Self;
How is it that what seems to be a std::io::Error -> Self seems to return a hyper::Error value, when none of the generics and arguments I give it are of the type hyper::Error?
It seems that some sort of implicit type conversion is happening even when I specify all the types explicitly?
Type information in Rust can flow backwards.
The return type of the closure is specified to be hyper::Error. Therefore, the result of the block must be hyper::Error, therefore the result of From::from must be hyper::Error.
If you wanted to, you could use ...
<hyper::Error as ::std::convert::From>::<std::io::Error>::from(x)
... which would be the even more fully qualified version. But with the closure return type there, it's unnecessary.
Type inference has varying degrees.
For example, in C++ each literal is typed, and only a fully formed type can be instantiated, therefore the type of any expression can be computed (and is). Before C++11, this led to the compiler giving an error message: You are attempting to assign a value of type X to a variable of type Y. In C++11, auto was introduced to let the compiler figure out the type of the variable based on the value that was assigned to it.
In Java, this works slightly differently: the type of a variable has to be fully spelled out, but in exchange when constructing a type the generic bits can be left out since they are deduced from the variable the value is assigned to.
Those two examples are interesting because type information does not flow the same way in both of them, which hints that there is no reason for the flow to go one way or another; there are however technical constraints aplenty.
Rust, instead, uses a variation of the Hindley Milner type unification algorithm.
I personally see Hindley Milner as a system of equation:
Give each potential type a name: A, B, C, ...
Create equations tying together those types based on the structure of the program.
For example, imagine the following:
fn print_slice(s: &[u32]) {
println!("{:?}", s);
}
fn main() {
let mut v = Vec::new();
v.push(1);
print_slice(&v);
}
And start from main:
Assign names to types: v => A, 1 => B,
Put forth some equations: A = Vec<C> (from v = Vec::new()), C = B (from v.push(1)), A = &[u32] OR <A as Deref>::Output = &[u32] OR ... (from print_slice(&v),
First round of solving: A = Vec<B>, &[B] = &[u32],
Second round of solving: B = u32, A = Vec<u32>.
There are some difficulties woven into the mix because of subtyping (which the original HM doesn't have), however it's essentially just that.
In this process, there is no consideration for going backward or forwarded, it's just equation solving either way.
This process is known as Type Unification and if it fails you get a hopefully helpful compiler error.
I have the following code, in which fac return (MyType, OtherType):
let l = (-1..13).map(|x| {
fac(x).0
}).collect::<Vec<MyType>>();
It works, but I'm throwing away the OtherType values. So I decided to use .unzip, like this:
let (v, r) = (-1..13).map(|x| {
fac(x)
}).unzip();
let l = v.collect::<Vec<MyType>>();
let q = r.collect::<Vec<OtherType>>();
But type inference fails with:
error: the type of this value must be known in this context
let l = v.collect::<Vec<Literal>>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~
let q = r.collect::<Vec<OtherType>>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~
The thing is: I don't know or care what is the concrete type of the iterators (and I would suppose the compiler could infer them, as shown in the first snippet). How to satisfy the compiler in this case?
Also, I would prefer to restructure the code - I don't like to separately call .collect() on both v and r. Ideally I would continue the method chain after .unzip(), returning two Vecs in that expression.
.unzip() doesn't return iterators — it acts like two parallel collect! You can in fact collect the two pieces to different kinds of collections, but let's use vectors for both in this example:
// Give a type hint to determine the collection type
let (v, r): (Vec<MyType>, Vec<OtherType>) = (-1..13).map(|x| {
fac(x)
}).unzip();
It is done this way to be as simple and transparent as possible. Returning two iterators instead would need them to share a common state, a complexity that rust's iterator library prefers to avoid.