Conversions

Rust is very strict on needing explicit conversions that can make a functional difference, but provides ample support for automatic dereferencing. You can't even add two integers of different types, you need an explicit conversion. This actually has a good reason: It makes sure the programmer knows exactly how the integers are added. For example, an 8-bit addition overflows much sooner than a 16-bit addition. Two integers do integer division in most other languages, even when assigned to a floating point target. Granted, you don't need to worry about those in most cases, but in edge cases, this is a significant feature for promoting correctness.

Arrays and slices are exclusively indexed by values of the usize type. The reason is that ultimately, usize covers the entire accessible memory pool while not hindering performance by being bigger than the hardware natively supports. Also, a memory address obviously can't be negative.

Primitive types can be converted to each other with the as keyword. For things like interpreting the bits of a floating point value as an integer, methods are provided. For composite types, there are two traits handling conversion from one type to another, From and Into, but more on that, well, you know the drill by now.


#![allow(unused)]
fn main() {
let array = [0; 64];
let small_number: i16 = 42;
let big_number: u64 = 69;
let boolean = true;

println!("{}", array[small_number as usize]);
println!("{}", big_number + small_number as u64);
println!("{}", boolean as usize);
}

There are two other traits, Defer and DeferMut, that enable structs (manually) and most reference types (automatically) to transparently present the element they wrap around/refer to. This doesn't work if you need the value of an inner field, but it works for methods and fields on the inner one. A good example would be the Box struct which gives access to its inner value, String which gives access to all the str methods, or Vec which also gives access to all slice methods of the same element type.

struct MyStruct {
  pub data: usize
}

fn example() {
  let mut random_string = String::from("This sHouLd aLl bE lOweR cAsE!");
  let mut value = 69;
  let my_struct = MyStruct {
    data: 42
  };
  let variable_reference = &mut value;
  let struct_reference = &my_struct;
  
  // Needs manual dereferencing
  *variable_reference += 1;
  // Calling str method on a String
  random_string.make_ascii_lowercase();
  
  println!("{}", random_string);
  // Special case because the Display trait
  // is also implemented on references
  println!("{}", variable_reference);
  // Does not need manual dereferencing
  println!("{}", struct_reference.data);
}
fn main() { example(); }

Last but not least, the ? operator can be used to escalate errors through a function. It can implicitly convert an error into a more general trait object and even box it as needed. However, as you probably already guessed, more in its own chapter.