Having spent a number of Codeforces rounds practicing the ins and outs of Rust, I think it's finally safe to encourage wider participation in the language! This guide is intended primarily for C++ programmers who may have taken interest in Rust, but had doubts regarding its feasibility in timed contests. On Quora, I summarized why I think Rust is suitable for contests in 2019 onward. So here I'll go over some aspects of my Codeforces submissions in more detail, with some tips along the way.
My solution to 1168C: And Reachability
My solution to 1158D: Winding polygonal line
Right away, you'll notice certain characteristics:
The only global variable is a compile-time constant. By scoping things appropriately, I don't have to worry about accidentally forgetting to reset data.
Very few mutable variables. This makes code easier to reason about, and mistakes less likely.
A polymorphic
Scanner.next()
method. It can read space-separated tokens of any type that implements the traitFromStr
.Output via
BufWriter
. This is needed for speed, if you have to write a large number of lines.BufWriter
flushes automatically when it goes out of scope, but you'll probably want to flush() manually on interactive problems!A mix of imperative-style and functional-style constructions, depending on which is clearer.
In Rust, you can read a Vec
(i.e., vector
in C++, ArrayList
in Java) of floats from standard input in imperative style:
let mut v = Vec::with_capacity(n);
for _ in 0..n {
let elem = scan.next::<f64>();
v.push(elem)
}
Or you can do it in functional style, rendering the result immutable:
let v: Vec<f64> = (0..n).map(|_| scan.next()).collect();
You can "consume" (i.e., move) a Vec
if you won't need it anymore:
for elem in v { // equivalent to v.into_iter().for_each(|elem| ...)
...
}
Or borrow its contents mutably, to change some of its elements:
for &mut elem in &mut v { // equivalent to v.iter_mut().for_each(|&mut elem| ...)
...
}
Or borrow its contents immutably:
for &elem in &v { // equivalent to v.iter().for_each(|&elem| ...)
...
}
You can also keep track of the index:
for (i, &elem) in v.iter().enumerate() {
...
}
Here's how to sum all the positive elements in functional style:
let answer = v.into_iter().filter(|&elem| elem > 0.0).sum();
Rust String
s are UTF-8. To get random access, you'll have to convert them to .chars()
or .bytes()
. And if you're reading a String
consisting entirely of 0s and 1s? There's an efficient way to convert them to bool
s:
let s: String = scan.next();
let v: Vec<bool> = s.chars().map(|ch| ch == ‘1’).collect();
As you can see, the language is very expressive, and the standard library very flexible. One very interesting finding from my experience with Rust is that "production-quality" code and "quick" code look much more alike than they do in C++. This is because Rust not only makes it harder to do things the wrong way, but it also makes it much easier to do things the right way. As a result, I naturally find myself coding in a better style, even under time pressure!
Overall, my solutions attain much fewer WA verdicts in Rust than they did in C++. Development time is sometimes more, sometimes less, but it gets better with practice. Try it out!
As additional resources, please check out my competitive programming codebook, and Rust's new dbg!() macro.
Leave your competitive Rust questions in the comments :)