My approach to competitive programming in Rust

Правка en1, от TecTrixer, 2024-02-03 05:24:19

My approach to competitive programming in Rust

Even though C++ is by far the most dominant competitive programming language today, Rust has become a viable alternative. I started using it for competitive programming a few months ago and I really like it. This is my take on using it for competitive programming, please feel free to ask questions in the comments or to add your own experiences.

There are many aspects which make a language well suitable for competitive programming:

1. Support

Without being supported by the biggest programming platforms such as Codeforces, AtCoder and LeetCode a language is mostly useless for competitive programming (this list is debatable ^^). It can still be used for platforms where you only submit the answer such as Project Euler or Advent of Code. But I can speak from experience that it really sucks to want to take part in an upcoming contest only to find out that you cannot use your favorite language (I'm looking at you USACO!). Luckily, by now most platforms accept submissions written in Rust. Most platforms even have quite recent versions of Rust.

2. Input / Output

The thing you should learn first about language for competitive programming is how to do input and output. Unfortunately, by default this is not too pretty in Rust:

let mut buffer = String::new();
let stdin = std::io::stdin();
stdin.read_line(&mut buffer).unwrap();
let x: usize = buffer.parse().unwrap();
println!("{x}");

Luckily, it is quite easy to create a good template for input and output. I encourage you to look at existing other templates and to then create your own template from scratch. This helps to make it fit perfectly for you, since everyone has slightly different preferences. If you are interested here is a link to my input template. You can then handle input and output with it like this:

let mut io = Io::new();
let (n, x) = r!(U, F); // n is now of type usize and x is of type f64
let chars = io.chars(); // chars is a Vec<char> which allows indexing into the string
let vec = io.vec::<I>(n); // vec is now a vector of n different isize integers
wf!("{n} {x}");
io.nl();
w!(chars);

3. How to build your project

Cargo is the recommended way to organize your Rust projects and build them correctly. For quite some time I always created a new project with cargo for every subtask of a contest. This quickly leads to unnecessarily large directories which each have their own build files, etc... . Another problem is that you cannot use crates with most competitive programming platforms. There exist some rust bundler projects (e.g. rust-bundler) but in my experience most of them have some shortcomings. Instead I switched to using a template with named binaries, this way I have a.rs, b.rs, ... files with some prewritten code. If you want to find out more, check out my template repository. There I also have a quick way to download tests and automatically locally test my solution against those. If you want me to explain that structure and its details, please let me know in the comments below :)

4. Language specifics

A big aspect of Rust is its very strict compiler which enforces memory safety. This could sometimes lead to complications, when a lifetime is missing or an already borrowed variable is being written to. After programming in Rust for a while, I only rarely encounter these though. You get used to most of the common errors and the code needed for competitive programming is surprisingly similar most of the time. A few things I learned while using Rust (these only apply to competitive programming, not to real world Rust development):

  • Always use Vec<char> instead of String since the former one can be indexed in O(1) instead of O(n).

  • Use the vec! macro often, it is your friend! Instead of creating a new Vec and then carefully pushing all the elements, just create a new array with zeros and index into it. Additionally, it is one of the only datastructures you will ever need (together with HashSet, HashMap, VecDeque and BTreeSet / BTreeMap).

  • Compile in debug mode when testing! This way you will get lots of useful panic information about problems such as overflows, underflows or invalid array indices and much more. In general, the compiler is your friend. It helps you and avoids you having to find out why your code is segfaulting even though you just added a print function.

  • Use iterators and the functional aspect of Rust. Often a somewhat complex for-loop can be simplified into a chained functional iterator, which is both shorter and more readable.

  • Don't be afraid to use unwrap(). You do not win in competitive programming by having the safest, most modular, most documented or most readable code. If you are being given an assumption in a task just ignore the other cases. Always add a short comment why you can ignore it though! I cannot count the amount of times this saved me from bug hunting because of an incorrect assumption. In fact, my input / output library uses unwrap a lot and even unsafe sometimes.

So in my opinion, Rust is actually a very viable competitive programming language. Especially if you put some time in it and get comfortable with the standard library and the specifics it feels really good to use and prevents you from making many unnecessary mistakes.

Hopefully, you enjoyed reading this post, I might write another blog post explaining my editor and automatic testing setup if there is interest in it. This is actually my first blog post here on codeforces, so please let me know how I can improve. I'm happy to answer any questions or comments :)

Have fun coding!

Теги rust, language, setup, progamming language

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en2 Английский TecTrixer 2024-02-03 13:53:38 0 (published)
en1 Английский TecTrixer 2024-02-03 05:24:19 6071 Initial revision (saved to drafts)