I found understanding Rust types really confusing, so I wrote up a small tutorial for myself in an attempt to understand some of them. This is by no means exhaustive. There is a types section in the manual, but it has nowhere near enough examples.
I'm not talking about managed pointers (@
) at all. A lot of the difficulty with Rust types is that the language is constantly changing, so this will likely be out of date soon.
First, a few preliminaries: it's easier to play with types if you have a REPL and can interactively check the types of objects. This isn't really possible in Rust, but there are workarounds.
There is no working Rust REPL. However, you can use this script to approximate one -- it compiles what you put into it and prints the result. You can't use the results of what you did previously, though. Save as rustci
.
If you want to find the type of a variable y
, run:
let x: () = y;
This will generate a compiler error with the type of y
. This is a hack, but as I understand it it's the best workaround for now.
For example, to find the type of a function in our fake REPL, you can do
fn f() {}; let x: () = f;
This will give us the error:
error: mismatched types: expected `()` but found `fn()` (expected () but found extern fn)
which tells us that the type of f
is fn()
. Yay!
This is an incomplete list.
Integers (signed and unsigned): int
, uint
, i8
, i16
, i32
, i64
, u8
, u16
, u32
, u64
Floats: f32
, f64
Booleans: bool
Primitive type examples
let x: uint = 2;
let y: u8 = 40;
let z: f32 = abc;
There are 3 possible types for a vector of u8
: [u8, ..N]
, &[u8]
, ~[u8]
.
[u8]
by itself is not a type.
[u8, ..5]
is a fixed-size vector of u8
of length 5.
Vector Examples
// Fixed size vector
let x : [uint, ..10] = [5, ..10]; // [5,5,5,5,5,5,5,5,5,5]
// Create a variable size owned vector
let mut numbers1 : ~[uint]= ~[0, 1, 2, 3, 4, 5];
// Create a variable size borrowed vector. This is also called a "vector slice".
let mut numbers2 : &[uint]= &[0, 1, 2];
let mut slice: &[uint] = numbers1.slice(0, 3);
Some string types include: &str
, ~str
, and &'static str
.
A string is represented internally as a vector of bytes. However, str
by itself is not a type, and there are no fixed-size strings. You can convert any of the string types to a byte vector &[u8]
.
char
is a 32-bit Unicode character.
String Examples
use std::option::Option;
// Static string
let hello: &'static str = "Hello!";
let hello2: &str = "Hello!";
// Owned string
let owned_hello: ~str = ~"Hello!";
// Borrowed string
let borrowed_hello = &owned_hello;
// Character
let c: char = 'a';
// Indexing into a string gives you a byte, not a character.
let byte: u8 = owned_hello[1];
// You need to create an iterator to get a character from a string.
let c: Option<char> = owned_hello.chars().nth(2);
// Switch to the string's representation as bytes
let bytes: &[u8] = owned_hello.as_bytes();
For a function fn(a: A) -> B
fn(A)->B
is a type, So are &(fn(A)->B)
, ~(fn(A)->B)
, but you need to add parens right now.
You probably only want to use fn(A)->B
, though.
Function type examples
fn foo(a: int) -> f32 {
return 0.0;
}
let bar: fn(int) -> f32 = foo;
let baz: &(fn(int) -> f32) = &foo;
The type of a closure mapping something of type A
to type B
is |A| -> B
. A closure with no arguments or return values has type ||
.
Closure type examples
let captured_var = 10;
let closure_no_args = || println!("captured_var={}", captured_var);
let closure_args = |arg: int| -> int {
println!("captured_var={}, arg={}", captured_var, arg);
arg
};
// closure_no_args has type ||
// closure_args has type |int| -> int
fn call_closure(c1: ||, c2: |int| -> int) {
c1();
c2(2);
}
call_closure(closure_no_args, closure_args);
For any type T
, *T
is a type.
Hi @jvns,
Thanks for sharing this tutorial. I assume the repl you refer to runs only in Linux environments?
thanks for your reply