usize vs u32: дуэль целочисленных типов в Rust
Если вы пишете на Rust хотя бы неделю, вы точно сталкивались с этой ситуацией: компилятор ругается, потому что вы пытаетесь использовать u32 для индексации вектора, а он требует usize. Или наоборот — вы передаёте usize в функцию, которая ждёт u32, и получаете красную волну в IDE.
На Stack Overflow вопрос «What's the difference between usize and u32?» набрал тысячи просмотров. Это не случайно — путаница между этими типами — одна из самых частых ловушек для новичков в Rust.
Давайте раз и навсегда разберёмся, чем они отличаются, когда какой использовать, и как не попасть впросак. Поехали!
Коротко: главное отличие
usize — это беззнаковое целое, размер которого зависит от архитектуры системы. На 64-битной системе это 8 байт, на 32-битной — 4 байта. u32 — это всегда 4 байта, независимо от платформы.
Но разница не только в размере. Главное — семантика:
usize— для размеров, индексов и количества элементов в коллекцияхu32— для числовых данных, которые не превышают 4 миллиарда
И да, компилятор не позволяет их смешивать без явного преобразования. Это не баг, а фича — так Rust защищает вас от ошибок, связанных с разрядностью архитектуры.
Почему usize существует?
Представьте, что вы пишете код, который должен работать и на старом 32-битном микроконтроллере, и на современном 64-битном сервере. Длина вектора на 32-битной системе не может превышать 2^32 - 1 элементов, а на 64-битной — 2^64 - 1.
Если бы вы использовали u32 для индексации на 64-битной системе, то просто не смогли бы обратиться к элементам за пределами 4 миллиардов — даже если памяти хватает. usize решает эту проблему: он всегда вмещает максимально возможный размер памяти на текущей архитектуре.
// Пример: usize автоматически подстраивается под архитектуру
fn main() {
let large_vec: Vec<u8> = vec![0; 1_000_000_000]; // 1 млрд элементов
// usize вмещает этот индекс на любой платформе
let index: usize = 999_999_999;
println!("Элемент: {}", large_vec[index]);
// А вот u32 может не хватить на 32-битной системе
// let bad_index: u32 = 999_999_999; // На 32-битной системе это скомпилируется, но...
// large_vec[bad_index as usize]; // ...придётся делать преобразование
}
Сравнение в таблице
| Характеристика | usize | u32 |
|---|---|---|
| Размер на 64-bit | 8 байт | 4 байта |
| Размер на 32-bit | 4 байта | 4 байта |
| Диапазон (64-bit) | 0 .. 2^64-1 | 0 .. 2^32-1 |
| Основное назначение | Индексы, размеры, длины | Числовые данные |
| Неявное приведение | Нет | Нет |
Когда использовать usize?
Всегда для индексации коллекций. Это правило практически без исключений. Если вы пишете vec[i], где i — это u32, компилятор скажет: «Эй, друг, давай-ка usize».
// Правильно: используем usize для индексов
fn get_element(vec: &[i32], index: usize) -> Option<&i32> {
vec.get(index)
}
// Неправильно: u32 для индексации — придётся преобразовывать
fn get_element_bad(vec: &[i32], index: u32) -> Option<&i32> {
vec.get(index as usize) // явное преобразование — костыль
}
fn main() {
let data = [10, 20, 30, 40];
// Так — ок
let idx: usize = 2;
println!("{}", get_element(&data, idx).unwrap());
// Так — ошибка компиляции
// let idx2: u32 = 2;
// println!("{}", get_element(&data, idx2).unwrap());
}
Другие случаи для usize:
- Возвращаемые значения
len(),count()у коллекций - Параметры для работы с памятью (аллокации, размеры буферов)
- Счётчики, которые потенциально могут превысить 4 миллиарда
Когда использовать u32?
u32 — ваш выбор, когда вы работаете с числовыми данными фиксированного размера:
- Идентификаторы записей в БД (если их меньше 4 млрд)
- Порты, коды ошибок, флаги
- Данные из бинарных протоколов (где размер поля жёстко задан)
- Экономия памяти в структурах (4 байта вместо 8)
#[derive(Debug)]
struct User {
id: u32, // ID пользователя — вряд ли будет > 4 млрд
name: String,
score: u32, // Очки — тоже в разумных пределах
}
fn main() {
let user = User {
id: 42,
name: "Алиса".to_string(),
score: 1500,
};
println!("Пользователь: {:?}", user);
// А вот для количества пользователей в системе — лучше usize
let total_users: usize = 1_000_000;
println!("Всего пользователей: {}", total_users);
}
Преобразование между usize и u32
Rust не делает неявных преобразований между целыми типами — это сознательное решение для безопасности. Но иногда преобразование необходимо. Вот как это делать правильно:
fn main() {
// u32 -> usize (всегда безопасно, т.к. usize >= u32 по размеру)
let small: