Here I try to translate a bit of code from a little Minesweeper game written in the interesting Whiley language (that contains a SMT solver). A cell of the Minesweeper matrix is represented as:
// An exposed square is one which has been exposed by the player, and
// displays its "rank". The rank is the count of bombs in the eight
// directly adjacent squares.
public type ExposedSquare is {
int rank,
bool holdsBomb
} where rank >= 0 && rank <= 8
public type HiddenSquare is {
bool holdsBomb,
bool flagged
}
public type Square is ExposedSquare | HiddenSquare
(Code from: https://github.com/Whiley/WyBench/blob/master/src/107_minesweeper/Minesweeper.whiley ).
To represent a rank in Rust I can use a u8, but it goes against the good idea of “Making illegal states unrepresentable” ( https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/ ). So I’ve used an enum:
#[derive(Debug)]
enum AdjacentBombs { B0, B1, B2, B3, B4, B5, B6, B7, B8 }
But to convert numbers from and to AdjacentBombs
you need bug-prone switches that are perhaps worse than using an u8
:
fn to_adjacent_bombs(n_bombs: u8) -> Option<AdjacentBombs> {
use AdjacentBombs::*;
match n_bombs {
0 => Some(B0),
1 => Some(B1),
2 => Some(B2),
3 => Some(B3),
4 => Some(B4),
5 => Some(B5),
6 => Some(B6),
7 => Some(B7),
8 => Some(B8),
_ => None,
}
}
fn to_n_bombs(ab: AdjacentBombs ) -> u8 {
use AdjacentBombs::*;
match ab {
B0 => 0,
B1 => 1,
B2 => 2,
B3 => 3,
B4 => 4,
B5 => 5,
B6 => 6,
B7 => 7,
B8 => 8,
}
}
fn main() {
let b1 = AdjacentBombs::B3;
println!("{:?}", b1);
let b2 = to_n_bombs(b1);
println!("{}", b2);
let b3 = to_adjacent_bombs(b2);
println!("{:?}", b3);
}
You can try to “improve” the code like this:
#[repr(u8)]
#[derive(Debug, Copy, Clone)]
enum AdjacentBombs {
B0 = 0,
B1 = 1,
B2 = 2,
B3 = 3,
B4 = 4,
B5 = 5,
B6 = 6,
B7 = 7,
B8 = 8,
}
fn to_adjacent_bombs<T>(n_bombs: T) -> Option<AdjacentBombs>
where usize: From<T> {
const ALL_BOMBS: [AdjacentBombs; 9] = [
AdjacentBombs::B0, AdjacentBombs::B1,
AdjacentBombs::B2, AdjacentBombs::B3,
AdjacentBombs::B4, AdjacentBombs::B5,
AdjacentBombs::B6, AdjacentBombs::B7,
AdjacentBombs::B8];
let n_bombs = usize::from(n_bombs);
if n_bombs > 8 { return None; }
Some(unsafe { *ALL_BOMBS.get_unchecked(n_bombs) })
}
fn to_n_bombs(ab: AdjacentBombs ) -> u8 {
ab as u8
}
Or with this:
fn to_adjacent_bombs(n_bombs: u8) -> Option<AdjacentBombs> {
if n_bombs > 8 { return None; }
Some(unsafe { std::mem::transmute::<u8, AdjacentBombs>(n_bombs) })
}
But it uses unsafe code, and it’s bug-prone, and long still.
There are also crates that try to help, like: https://crates.io/crates/enum_primitive
This seems a bit better solution, that could be supported:
use std::convert::TryFrom;
#[repr(u8)]
#[derive(Debug, Copy, Clone, TryFrom)]
enum AdjacentBombs {
B0 = 0,
B1,
B2,
B3,
B4,
B5,
B6,
B7,
B8,
}
fn main() {
let b1 = AdjacentBombs::B3;
println!("{:?}", b1);
let b2 = u8::from(b1);
println!("{}", b2);
let b3 = AdjacentBombs::try_from(b2);
println!("{:?}", b3);
}
But for the type system of a reliable language like Rust I really prefer intervals (subsets) of integral values (using a hypotetical syntax):
enum Cell {
Exposed { holds_bomb: bool, rank: u8[0 ... 8] },
Hidden { holds_bomb: bool, flagged: bool },
}
(A different and more general solution is to introduce refinement types in Rust, but it’s probably too much complex for general Rust code and compiler).
(To avoid “boolean blindness” ( https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/ ) you can also replace those bools with FlagState { Unflagged, Flagged }
and BombPresence { Missing, Present }
enumerations, but this is less important in this specific data structure and program).
Once you have defined a subset type:
type Rank = u8[0 .. 9];
you should be able to convert Rank->u8 with a u8::from()
and u8->Option with a Rank::try_from()
.