Skip to content
Select theme

How to choose between i32 and usize

Short answer: use i32 for numbers you do arithmetic with — it is the default integer type, and it can be negative. Use usize for anything that measures memory: indices, lengths, capacities. Rust does not leave this as a style choice — indexing a Vec with an i32 does not compile, and len() hands you a usize whether you wanted one or not.

From a fresh project — cargo new usize-demo, then replace src/main.rs with each block below.

i32 is 4 bytes wide
usize is 8 bytes wide on this machine
-42 3

i32 is signed and exactly 32 bits on every platform: −2,147,483,648 to 2,147,483,647. Write let x = 5; with no type annotation and this is the type you get — the compiler’s default.

usize is unsigned, and its size matches a pointer: 4 bytes on a 32-bit target, 8 on a 64-bit one. This machine is 64-bit, so the run above prints 8. Its maximum is the largest offset a memory address can express, which is why it — and not i32 — is the type of every length, index, and capacity in the standard library: a collection can never hold more elements than there are addressable positions.

The unsigned part matters too: a usize has no negative values, so let i: usize = -1; is a compile error, not a wrapped-around giant number.

The most common encounter is indexing. This does not compile:

Terminal window
cargo run -q
error[E0277]: the type `[{integer}]` cannot be indexed by `i32`
--> src/main.rs:4:22
|
4 | println!("{}", v[i]);
| ^ slice indices are of type `usize` or ranges of `usize`
|
= help: the trait `SliceIndex<[{integer}]>` is not implemented for `i32`
help: `usize` implements trait `SliceIndex<T>`
--> /Users/rango/.rustup/toolchains/1.99.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/slice/index.rs:179:1
|
179 | const unsafe impl<T> SliceIndex<[T]> for usize {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SliceIndex<[T]>`
|
::: /Users/rango/.rustup/toolchains/1.99.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/bstr/traits.rs:197:1
|
197 | unsafe impl SliceIndex<ByteStr> for usize {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SliceIndex<ByteStr>`
= note: required for `Vec<{integer}>` to implement `Index<i32>`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `usize-demo` (bin "usize-demo") due to 1 previous error

Indexing goes through the SliceIndex trait, and i32 does not implement it — only usize (and ranges of usize) do, because indexing is a memory-address calculation. The fix is to make the variable the right type in the first place when you can:

20

When the index comes from a loop, you rarely write the type at all — enumerate already produces a usize:

0: 90
1: 85
2: 77

Even without indexing, the two types refuse to meet in one expression. len() returns usize; compare an i32 against it and the compiler stops you:

Terminal window
cargo run -q
error[E0308]: mismatched types
--> src/main.rs:4:18
|
4 | let ok = n < v.len();
| - ^^^^^^^ expected `i32`, found `usize`
| |
| expected because this is `i32`
|
help: you can convert a `usize` to an `i32` and panic if the converted value doesn't fit
|
4 | let ok = n < v.len().try_into().unwrap();
| ++++++++++++++++++++
For more information about this error, try `rustc --explain E0308`.
error: could not compile `usize-demo` (bin "usize-demo") due to 1 previous error

The same wall stands between the types for +, - and every other operator — there is no implicit conversion anywhere in Rust. The compiler’s own suggestion (try_into().unwrap()) works, but it panics on values that do not fit, which trades a compile-time error for a run-time one. The better fix is the one from the indexing error: pick one type for the job. If n counts positions too, declare it usize and the expression just works.

Sometimes you genuinely have an i32 — a value parsed from input, a number from an API — and need a usize. The tempting fix is as usize:

18446744073709551615

A negative number becomes a 19-digit one. as from a signed type to an unsigned one wraps around: −1 becomes the largest value usize can hold. The compiler allows it, and nothing at run time warns you — the bug surfaces much later, when that “index” walks off the end of a slice or quietly compares as huge.

TryFrom makes the failure a value you have to deal with instead:

no: number too small to fit in target type

usize::try_from(n) returns a Result: Ok for every value that fits, Err for the negatives (and, going the other way, for values too big for the target). It is the same shape as the parse result in How to convert a String to a number — conversion that can fail returns the failure as a value.

  • Counting positions in memory — indices, len(), capacities, loop counters over a collection — usize. The standard library already chose it; matching it means no casts at all.
  • Numbers with their own meaning — temperatures, scores, user input, anything that can legitimately be negative — i32. It is the default, it fits almost every real-world range, and it will not silently become 18 quintillion.
  • Between them, convert with try_from, not as. as is for the rare case where wrapping is exactly what you want; for everything else it turns a type error into a wrong number.

The overflow question — what happens when a number grows past its type’s limit — is How to stop an integer from overflowing’s subject; the same limits apply to both types here, they just sit in different places.

  • Whether to use f32 or f64 is the same question one row down, for floats — and there the default is different.
  • Working with numbers — the rest of the chapter.