Better rust starting code

Currently rust starter code for processing n lines goes:

use std::io;

macro_rules! parse_input {
    ($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap())
}

fn main() {
    // ...
    let mut input_line = String::new();
    io::stdin().read_line(&mut input_line).unwrap();
    let n = parse_input!(input_line, i32);
    for i in 0..n as usize {
        let mut input_line = String::new();
        io::stdin().read_line(&mut input_line).unwrap();
        let line = input_line.trim_matches('\n').to_string();
    }
    // ...
}

I’m pretty sure the trim and trim_matches are both just to remove the newline at the end, in which case, I’m changing both to trim_end_matches. And why not refactor and use ty for type in the macro to allow more complex types while we’re at it?

use std::io;

macro_rules! read_line {
    ($t:ty) => {{
        let mut input_line = String::new();
        io::stdin().read_line(&mut input_line).unwrap();
        input_line.trim_end_matches('\n').parse::<$t>().unwrap()
    }};
}

fn main() {
    // ...
    let n = read_line!(i32);
    for i in 0..n as usize {
        let line = read_line!(String);
    }
    // ...
}
2 Likes

Oh, actually this might happen after a while which would basically do the same thing except without parse

1 Like

This is a great idea.
Starting code should be the most understandable, and simplifying it is a great idea!

Looking at Create Starter.rs by rb5014 Ā· Pull Request #7 Ā· CodinGame/FallChallenge2022-KeepOffTheGrass Ā· GitHub I realize there’s also the situation of multiple inputs in one line like 13 4 106 239

EDIT: The previous code I wrote errors, here’s a fix (full code)

use std::io;

fn read_line() -> String {
    let mut input_line = String::new();
    io::stdin().read_line(&mut input_line).unwrap();
    input_line.trim_end_matches('\n').to_owned()
}

macro_rules! parse_line {
    ($x:expr, $($i:ident: $t:ty),*) => {
        let mut inputs = $x.split(' ');
        $(
            let $i = inputs.next().unwrap().parse::<$t>().unwrap();
        )*
    }
}


fn main() {
    let inputs = read_line();
    parse_line!{inputs, width: i32, height: i32};

    // game loop
    loop {
        let inputs = read_line();
        parse_line!{inputs, my_matter: i32, opp_matter: i32};
        for i in 0..height as usize {
            for j in 0..width as usize {
                let inputs = read_line();
                parse_line!{inputs,
                    scrap_amount: i32,
                    owner: i32, // 1 = me, 0 = foe, -1 = neutral
                    units: i32,
                    recycler: i32,
                    can_build: i32,
                    can_spawn: i32,
                    in_range_of_recycler: i32
                };
            }
        }

        println!("WAIT");
    }
}

Wow, Rust’s hygienic macro system (tagging inputs as in a separate context from $x and $i) is so convenient.

There’s still some limitations, for example there’s probably some way to allow trailing commas, and parsing a single value is inefficient because of an unnecessary split call (well that might be optimized away idk). parse_line!{inputs, something: i32}

Though looks like codingame handles non-split lines different so there could be custom code like
let something: i32 = inputs.parse().unwrap();

CodinGame’s stub generator (which generates the starting code) has to be flexible enough to be working for ALL programming languages that it supports. If your suggested code can fit into their generator well, I think they may consider adopting it :slight_smile:
https://www.codingame.com/playgrounds/40701/help-center/stub-generator

I think I gave everything except loop_line which for some reason doesn’t have an example in the link you gave. I think it’s just loop <amount> read <variable_sequence> in which case ez.

The macros have changed to read_line and parse_line see above for code.

The only other difference is

  • read <variable sequence>
    corresponds to let inputs = read_line(); and then parse_line!{inputs, scrap_amount: i32, owner: i32, units: i32, /* etc no trailing comma */}; An example is shown in my previous comment.
    This also works for cases where only one variable is read, since split doesn’t do anything. (parse_line!{inputs, one_var: i32}). (The types are as before). (Optimization below).
    The exception is read v:string(256) (the only input that can have spaces, and must be by itself), it corresponds to let v = read_line();

These are the same as before (I realize there’s not really a way to remove that as usize based on the spec)

  • write <text>
    = println!(<text>);
  • gameloop
    = put everything afterwards in loop { <stuff> }
  • loop <amount> <command>
    = for let i in 0..<amount> as usize { <command> }

Regular comments are preprocessed away (documentation comments can’t document variables) so no worry about that.


Though the single input parse_line can be optimized by adding a special case:

macro_rules! parse_line {
    /* special case: */
    ($x:expr, $i:ident: $t:ty) => {
        let $i = $x.parse::<$t>().unwrap();
    };

    ($x:expr, $($i:ident: $t:ty),*) => {
        let mut inputs = $x.split(' ');
        $(
            let $i = inputs.next().unwrap().parse::<$t>().unwrap();
        )*
    }
}

this would also make let v = read_line(); equivalent to parse_line!{inputs, v: String}; (which might simplify the implementation) because inputs.parse::<String>() optimizes into a noop.


in the future read_line!() might be replaced with io::input_ln().unwrap(), see the second comment/rfc link above.

In the stub generator,
• loop n read x: int is reading n lines, each line containing one value of x.
• loopline n read x: int is reading one line, which contains n values of x, separated by spaces.

If you think your code fits, you may tag _CG_ClementHammel to have a look :slight_smile:

@_CG_ClementHammel

Top level code

use std::io;

// Previously this would be repeated every time
fn read_line() -> String {
    let mut input_line = String::new();
    io::stdin().read_line(&mut input_line).unwrap();
    input_line.trim_end_matches('\n').to_owned()
}

// I have no idea how booleans were previously handled,
// so here's a somewhat hacky solution,
// the braces are necessary to make {bool} not a type
macro_rules! parse_input {
    ($x:expr, {bool}) => {
        $x == "1"
    };

    ($x:expr, $t:ty) => {
        $x.parse::<$t>().unwrap()
    }
}

// Possibly unnecessary
macro_rules! read_parse {
    ($($i:ident: $t:tt),*) => {
        let input_line = read_line();
        let mut inputs = input_line.split(' ');
        $(
            let $i = parse_input!{inputs.next().unwrap(), $t};
        )*
    }
}

read v:string

let v = read_line();

Previously it was already a special case (input_line.trim_matches('\n').to_string()) so this is an improvement.

read <variable sequence>

Two examples:

/* read a:int */
read_parse!{a:i32};

/* read b:float c:long d:word(32) e:bool */
read_parse!{b:f32, c:i64, d:String, e:{bool}};

Types are the same, except bool; by default there would a ParseBoolError. An actual implementation would use more line breaks so that comments can be added.

read_parse feels a little bit extra. It expands to

let input_line = read_line();
let mut inputs = input_line.split(' ');
let b = parse_input!(inputs.next().unwrap(), f32);
let c = parse_input!(inputs.next().unwrap(), i64);
let d = parse_input!(inputs.next().unwrap(), String);
//etc

for speed, but if read_parse isn’t used then really the old version (+read_line) is better

let input_line = read_line();
let inputs = input_line.split(' ').collect::<Vec<_>>();
let b = parse_input!(inputs[0], f32);
let c = parse_input!(inputs[1], i64);
let d = parse_input!(inputs[2], String);
//etc

The old solution uses split(" ") but classically, this causes a clippy::perf::single_char_pattern lint which says to use split(' ') because it’s faster (50% faster but by nanoseconds).

let input_line = read_line(); can’t be inlined ugh

gameloop

same as before

loop { /* stuff */ }

loop <amount> <command>

same as before

for i in 0..v as usize {
    /* stuff */
}

loopline <amount> <variable sequence>

let inputs = read_line();

// prev
for i in inputs.split_whitespace() {
    let state = parse_input!(i, i32);
}

// now
for i in inputs.split(' ') {
    let state = parse_input!(i, i32);
}

write

same as before

another thing (Rust debug optimizations)

Separately, really want to mention there’s a way to compile in optimized debug mode, with all the debug features!

Simply

[profile.dev]
opt-level = 3

in the Cargo.toml

https://doc.rust-lang.org/cargo/reference/profiles.html

Edit: Turns out it’s always in (non-optimized) debug mode even when submitting.

2 Likes

Of course for some reason coding game does not use cargo… I don’t understand why. Cargo is an incredible feature of rust.

they could also do with rust edition=ā€œ2021ā€, the latest version of rust, target-cpu=native, an extra library or ten like they have for python

I gave up in the last competition as I had to constantly butcher my pretty code to get it to run on coding game. 2021 and all the small recent changes are such massive improvements.

2 Likes