🏷️

Type System β€” What You Gain by Giving Up Dynamic Typing

Ruby duck typing vs Rust static type inference

In Ruby, x = "hello" can become x = 42 with zero complaints. Methods are all that matter (duck typing). In Rust, this is impossible.

let x = "hello";  // compiler infers &str
// x = 42;  // compile error! expected &str, found integer

Basic types

Ruby's Integer, Float, String split into finer types in Rust.

Ruby Rust Note
Integer i32, i64, u32, u64 etc. sign/size explicit
Float f32, f64 32-bit/64-bit
true/false bool same
String String, &str two kinds (separate post)
nil none (Option<T>) separate post
Symbol none (use enum) separate post
Array Vec<T> generic
Hash HashMap<K, V> generic

i32 is a 32-bit signed integer, u64 is a 64-bit unsigned integer. In Ruby you never think about this. In Rust, memory size and sign are explicit.

Type inference

Rust infers types in most cases without annotation.

let x = 42;          // inferred as i32 (default)
let y = 3.14;         // inferred as f64 (default)
let name = "sehwa";   // inferred as &str
let nums = vec![1, 2, 3]; // inferred as Vec<i32>

Sometimes annotation is needed β€” when the compiler can't decide.

// error β€” is 42 i32 or u64?
let x = "42".parse(); // error: type annotations needed

// tell it explicitly
let x: i32 = "42".parse().unwrap();
// or
let x = "42".parse::<i32>().unwrap();  // turbofish syntax

::<i32> is called turbofish. Because it looks like a fish. Rust community naming at its finest.

Tuples and arrays

Ruby arrays can mix types. [1, "hello", true] is fine. Rust's Vec holds one type only.

For multiple types, use tuples.

let pair: (i32, &str) = (42, "hello");
println!("{}", pair.0);  // 42
println!("{}", pair.1);  // hello

Ruby's return a, b pattern maps to tuples in Rust.

fn divide(a: f64, b: f64) -> (f64, f64) {
    (a / b, a % b)  // quotient and remainder
}
let (quotient, remainder) = divide(10.0, 3.0);

Type conversion β€” as and From/Into

In Ruby, 42.to_s and "42".to_i convert freely. Rust has two approaches.

// numeric conversion β€” as (like .to_i/.to_f)
let x: i32 = 42;
let y: f64 = x as f64;

// string β†’ number β€” parse (returns Result since it can fail)
let n: i32 = "42".parse().unwrap();

// number β†’ string β€” to_string
let s: String = 42.to_string();

as is for numeric conversions only. String conversions use parse() or to_string(). Unlike Ruby's .to_i, parse() returns a Result β€” you can't ignore conversion failure.

Key Points

1

Default integer is i32, sign/size are explicit

2

Type inference is powerful β€” rarely need annotations

3

Use tuples (i32, &str) to bundle multiple types

4

Type conversion is explicit: as (numeric), parse() (string→number)

Pros

  • Type errors caught at compile time β€” no more Ruby TypeError
  • IDE autocomplete works perfectly β€” far more accurate than Ruby

Cons

  • Missing Ruby's free type switching feels frustrating at first
  • Adding i32 and i64 requires explicit conversion

Use Cases

When i32 vs i64 matters in performance-sensitive code Type conversion when parsing external data (JSON, CSV)