usize vs u32 в Rust: в чем разница и что выбрать

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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:                

Блоги

Рекомендаций по книгам