Primitive Types
C is notoriously a little vague about type names:
#include <stdio.h>
int main() {
char c = 'a';
unsigned char uc = 12;
int i = 123;
unsigned int ui = 123;
short s = 123;
unsigned short us = 123;
long l = 123;
unsigned long ul = 123;
printf("%d %ld\n", c, sizeof(c));
printf("%d %ld\n", uc, sizeof(uc));
printf("%d %ld\n", i, sizeof(i));
printf("%d %ld\n", ui, sizeof(ui));
printf("%d %ld\n", s, sizeof(s));
printf("%d %ld\n", us, sizeof(us));
printf("%ld %ld\n", l, sizeof(l));
printf("%ld %ld\n", ul, sizeof(ul));
return 0;
}
The output will actually vary by platform. On my 64-bit Linux system I get:
97 1
12 1
123 4
123 4
123 2
123 2
123 8
123 8
Many C programmers prefer to be a bit more specific and use specifically sized types instead:
#include <stdio.h>
#include <stdint.h>
int main() {
int8_t c = 'a';
uint8_t uc = 12;
int32_t i = 123;
uint32_t ui = 123;
int16_t s = 123;
uint16_t us = 123;
int64_t l = 123;
uint64_t ul = 123;
printf("%d %ld\n", c, sizeof(c));
printf("%d %ld\n", uc, sizeof(uc));
printf("%d %ld\n", i, sizeof(i));
printf("%d %ld\n", ui, sizeof(ui));
printf("%d %ld\n", s, sizeof(s));
printf("%d %ld\n", us, sizeof(us));
printf("%ld %ld\n", l, sizeof(l));
printf("%ld %ld\n", ul, sizeof(ul));
return 0;
}
That's a bit more specific. If you're writing cross-platform code, it's often helpful to know exactly how large a variable is!
Additionally, size_t
defines the "integral type"---an integer that matches the size of your platform. So on 64-bit Linux, size_t
is 64-bits. On 32-bit Linux, it is 32-bits.
Rust Primitive Types
Rust only defines the explicitly sized types (and usize
/isize
for the integral type):
use std::mem::size_of; fn main() { let c: i8 = 97; let uc: u8 = 10; let i: i32 = 123; let ui: u32 = 123; let s: i16 = 123; let us: u16 = 123; let l: i64 = 123; let ul: u64 = 123; let vl: i128 = 123; let uvl: u128 = 123; let is: isize = 123; let us: usize = 123; let f: f32 = 123.4; let d: f64 = 123.4; println!("{c}, {}", size_of::<i8>()); println!("{uc}, {}", size_of::<u8>()); println!("{i}, {}", size_of::<i32>()); println!("{ui}, {}", size_of::<u32>()); println!("{s}, {}", size_of::<i16>()); println!("{us}, {}", size_of::<u16>()); println!("{l}, {}", size_of::<i64>()); println!("{ul}, {}", size_of::<u64>()); println!("{vl}, {}", size_of::<i128>()); println!("{uvl}, {}", size_of::<u128>()); println!("{is}, {}", size_of::<isize>()); println!("{us}, {}", size_of::<usize>()); println!("{f}, {}", size_of::<f32>()); println!("{d}, {}", size_of::<f64>()); }
We'll talk about the
use
in more detail later. You are importing a single function from thestd::mem
namespace. There's no copy/paste. You could also typestd::mem::size_of
every time.
Some takeaways:
- Each type is explicitly defined as
i<size>
oru<size>
. - Bytes (
u8/i8
) are not chars! We'll talk aboutchar
in a bit. They are special! println!
can take variable names (but not complex expressions) in{name}
, or you can use{}
as a placeholder and fill in the blanks as a parameter.println!
defies Rust's normal syntax, which is why its a macro!
Auto
C++ introduced auto
. So you can do:
auto i = 5;
That makes the compiler figure out what i
is, based on its usages (or a default). Rust las the same thing, you don't have to specify a type at each declaration. This is perfectly valid (and easier to read/type):
fn main() { let n = 123; }
Rust also supports suffixes for specifying type:
fn main() { let i = 123i32; let f = 123.4f32; let d = 123.4f64; }