
Популярность Rust неуклонно растёт, а с ней и сопутствующая экосистема. И оно не удивительно, ведь это единственный современный язык, который обеспечивает безопасность памяти и многопоточной обработки на этапе компиляции, предоставляя мощную и богатую систему сборки (cargo), а также всё больше различных пакетов (крейтов).
В своей повседневной работе я по-прежнему придерживаюсь C++, так как основная её часть связана с низкоуровневым программированием систем и ядра, а из этого языка легко задействовать написанный на С Windows API и COM API.
Rust — это язык для системного программирования, то есть он может справляться с теми же задачами, что и C/C++. Основное неудобство при этом создаёт громоздкий синтаксис, необходимый для преобразования типов С в типы Rust. Но это неудобство можно преодолеть, используя подобающие обёртки и макросы.
Короче говоря, я решил попробовать написать простой и полезный драйвер WDM. Это будет Rust-версия драйвера «Booster», о котором я пишу в своей книге (Windows Kernel Programming), позволяющего изменять приоритет любого потока на любое значение.
▍ Подготовка
Информация о всём необходимом для написания драйверов есть в репозитории windows-drivers-rs. В первую очередь у вас должна быть установлена WDK (стандартная или EWDK). Кроме того, документация требует установки LLVM для получения доступа к компилятору Clang. Далее я буду исходить из предположения, что всё это у вас установлено. Можно начать с создания проекта библиотеки Rust (ведь в техническом смысле драйвер — это DLL-файл, загружаемый в пространство ядра):
cargo new --lib booster
Откроем каталог Booster в VS Code и приступим к написанию кода. Но для его успешной компиляции и компоновки сначала нужно сделать так, чтобы файл
build.rs
просил cargo
производить статическую линковку в CRT. Добавьте build.rs
в корневой каталог Booster:fn main() -> Result<(), wdk_build::ConfigError> {
std::env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
wdk_build::configure_wdk_binary_build()
}
Теперь добавьте в
cargo.toml
необходимые зависимости. Вот их минимальный набор, которым мне удалось обойтись:[package]
name = "booster"
version = "0.1.0"
edition = "2021"
[package.metadata.wdk.driver-model]
driver-type = "WDM"
[lib]
crate-type = ["cdylib"]
test = false
[build-dependencies]
wdk-build = "0.3.0"
[dependencies]
wdk = "0.3.0"
wdk-macros = "0.3.0"
wdk-alloc = "0.3.0"
wdk-panic = "0.3.0"
wdk-sys = "0.3.0"
[features]
default = []
nightly = ["wdk/nightly", "wdk-sys/nightly"]
[profile.dev]
panic = "abort"
lto = true
[profile.release]
panic = "abort"
lto = true
Самые важные здесь — это зависимости крейтов.
Теперь перейдём к фактическому коду в
lib.rs
.▍ Код
Начнём с исключения стандартной библиотеки, так как в ядре её нет:
#![no_std]
Далее добавим несколько инструкций
use
, чтобы немного разгрузить код:use core::ffi::c_void;
use core::ptr::null_mut;
use alloc::vec::Vec;
use alloc::{slice, string::String};
use wdk::*;
use wdk_alloc::WdkAllocator;
use wdk_sys::ntddk::*;
use wdk_sys::*;
Крейт
wdk_sys
предоставляет низкоуровневые функции ядра, а крейт wdk
— высокоуровневые обёртки. Интересна здесь инструкция alloc::vec::Vec
. Из-за невозможности задействовать стандартную библиотеку вы можете решить, что типы вроде std::vec::Vec<>
недоступны, и в техническом плане будете правы. Хотя по факту Vec
определён в низкоуровневом модуле alloc::vec
, который можно использовать вне стандартной библиотеки. Это сработает, поскольку единственным требованием для
Vec
является наличие способа аллокации/деаллокации памяти. Rust обеспечивает эту возможность через глобальный объект аллокатора, который может предоставлять любой компонент кода. Но так как стандартная библиотека нам недоступна, глобального аллокатора нет, а значит, его нужно обеспечить. Тогда Vec
(и String
) смогут нормально функционировать:#[global_allocator]
static GLOBAL_ALLOCATOR: WdkAllocator = WdkAllocator;
Это глобальный аллокатор, предоставляемый крейтами WDK, который по аналогии с ручным подходом реализует аллокацию с помощью
ExAllocatePool2
и ExFreePool
.Далее мы добавляем два крейта
extern
, чтобы получить поддержку аллокатора и механизма обработки паники — который тоже нужно предоставить из-за отсутствия стандартной библиотеки. В Cargo.toml
есть установка для сброса драйвера (остановки системы) в случае паники кода:extern crate wdk_panic;
extern crate alloc;
Теперь перейдём к написанию самого кода и начнём с
DriverEntry
, точки входа любого драйвера ядра Windows: #[export_name = "DriverEntry"]
pub unsafe extern "system" fn driver_entry(
driver: &mut DRIVER_OBJECT,
registry_path: PUNICODE_STRING,
) -> NTSTATUS {
Те, кто знаком с драйверами ядра, частично узнают эту сигнатуру функции. Согласно правилу именования функций Rust в форме
snake_case
, называется она driver_entry
, но так как компоновщик ищет DriverEntry
, мы декорируем эту функцию атрибутом export_name
. Как вариант, можете использовать DriverEntry
и просто игнорировать или отключить предупреждения компилятора. Можно применить привычный макрос
println!
, повторно реализуемый через вызов DbgPrint
, как вы бы поступили в случае C/C++. Впрочем, ничто не мешает вам всё же вызвать DbgPrint
, но println!
как-то проще:println!("DriverEntry from Rust! {:p}", &driver);
let registry_path = unicode_to_string(registry_path);
println!("Registry Path: {}", registry_path);
К сожалению, похоже,
println!
ещё не поддерживает UNICODE_STRING
, поэтому напишем функцию unicode_to_string
для преобразования UNICODE_STRING
в обычную строку Rust:fn unicode_to_string(str: PCUNICODE_STRING) -> String {
String::from_utf16_lossy(unsafe {
slice::from_raw_parts((*str).Buffer, (*str).Length as usize / 2)
})
}
Возвращаясь в
DriverEntry
, далее мы создаём объект устройства с именем \Device\Booster:let mut dev = null_mut();
let mut dev_name = UNICODE_STRING::default();
string_to_ustring("\\Device\\Booster", &mut dev_name);
let status = IoCreateDevice(
driver,
0,
&mut dev_name,
FILE_DEVICE_UNKNOWN,
0,
0u8,
&mut dev,
);
Функция
string_to_ustring
превращает строку Rust в UNICODE_STRING
:fn string_to_ustring(s: &str, uc: &mut UNICODE_STRING) -> Vec<u16> {
let mut wstring: Vec<_> = s.encode_utf16().collect();
uc.Length = wstring.len() as u16 * 2;
uc.MaximumLength = wstring.len() as u16 * 2;
uc.Buffer = wstring.as_mut_ptr();
wstring
}
Это может показаться излишней сложностью, но ведь мы пишем эту функцию всего раз, и потом просто при необходимости используем. Хотя, возможно, подобная функция уже есть, и я просто недостаточно искал. Но для нашего драйвера вполне сойдёт и написанная выше.
В случае провала создания устройства возвращаем состояние ошибки:
if !nt_success(status) {
println!("Error creating device 0x{:X}", status);
return status;
}
nt_success
аналогичен макросу NT_SUCCESS
, предоставляемому заголовочными файлами WDK.Теперь создадим символическую ссылку, чтобы стандартный вызов
CreateFile
мог открывать дескриптор нашего устройства:let mut sym_name = UNICODE_STRING::default();
let _ = string_to_ustring("\\??\\Booster", &mut sym_name);
let status = IoCreateSymbolicLink(&mut sym_name, &mut dev_name);
if !nt_success(status) {
println!("Error creating symbolic link 0x{:X}", status);
IoDeleteDevice(dev);
return status;
}
Осталось инициализировать объект устройства с поддержкой буферизованного ввода/вывода (для простоты сделаем это с помощью
IRP_MJ_WRITE
), настроить процедуру выгрузки драйвера и основные нужные нам функции: (*dev).Flags |= DO_BUFFERED_IO;
driver.DriverUnload = Some(boost_unload);
driver.MajorFunction[IRP_MJ_CREATE as usize] = Some(boost_create_close);
driver.MajorFunction[IRP_MJ_CLOSE as usize] = Some(boost_create_close);
driver.MajorFunction[IRP_MJ_WRITE as usize] = Some(boost_write);
STATUS_SUCCESS
}
Обратите внимание на использование типа Rust
Option<>
для обозначения присутствия обратного вызова. Процедура выгрузки выглядит так:
unsafe extern "C" fn boost_unload(driver: *mut DRIVER_OBJECT) {
let mut sym_name = UNICODE_STRING::default();
string_to_ustring("\\??\\Booster", &mut sym_name);
let _ = IoDeleteSymbolicLink(&mut sym_name);
IoDeleteDevice((*driver).DeviceObject);
}
Здесь мы просто вызываем
IoDeleteSymbolicLink
и IoDeleteDevice
, как это делал бы стандартный драйвер ядра.▍ Обработка запросов
Нам нужно обрабатывать три вида запросов:
IRP_MJ_CREATE
, IRP_MJ_CLOSE
и IRP_MJ_WRITE
. Первые два реализуются просто — достаточно успешно завершить IRP:unsafe extern "C" fn boost_create_close(_device: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS {
(*irp).IoStatus.__bindgen_anon_1.Status = STATUS_SUCCESS;
(*irp).IoStatus.Information = 0;
IofCompleteRequest(irp, 0);
STATUS_SUCCESS
}
IoStatus
— это IO_STATUS_BLOCK
, но определён он с помощью union
, включающим Status
и Pointer
. Похоже на ошибку, так как в union
с Pointer
должна быть Information
(а не Status
). Как бы то ни было, этот код обращается к члену
Status
через «автоматически сгенерированное» объединение и выглядит убого. Сей момент определённо ещё нужно будет доработать. Но самое главное, что оно работает. Действительно же интересна функция обработчика IRP_MJ_WRITE
, который и отвечает за фактическую смену приоритета потоков. Сначала определим структуру запроса к драйверу:#[repr(C)]
struct ThreadData {
pub thread_id: u32,
pub priority: i32,
}
Важно использовать
repr(C)
, чтобы поля были выстроены в памяти также, как в случае C/C++. Это позволит обращаться к драйверу клиентам, написанным не на Rust. Собственно, далее я протестирую этот драйвер с имеющимся у меня клиентом, использующим его версию на C++. Драйвер получает ID потока, который нужно изменить, и соответствующий приоритет. Теперь можно начать с boost_write
:unsafe extern "C" fn boost_write(_device: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS {
let data = (*irp).AssociatedIrp.SystemBuffer as *const ThreadData;
Поскольку мы реализовали поддержку буферизации ввода/вывода, первым делом получаем указатель данных из
SystemBuffer
в IRP. Это копия буфера клиента из пространства ядра. Затем проверяем ошибки:let status;
loop {
if data == null_mut() {
status = STATUS_INVALID_PARAMETER;
break;
}
if (*data).priority < 1 || (*data).priority > 31 {
status = STATUS_INVALID_PARAMETER;
break;
}
Инструкция
loop
создаёт «бесконечный блок», из которого можно выйти через break
. Убедившись, что приоритет вписывается в заданный диапазон, переходим к обнаружению объекта потока:let mut thread = null_mut();
status = PsLookupThreadByThreadId(((*data).thread_id) as *mut c_void, &mut thread);
if !nt_success(status) {
break;
}
Здесь мы используем
PsLookupThreadByThreadId
. Если безуспешно, то такого ID, скорее всего, нет — выходим. Теперь остаётся настроить приоритет и завершить запрос с имеющимся статусом. KeSetPriorityThread(thread, (*data).priority);
ObfDereferenceObject(thread as *mut c_void);
break;
}
(*irp).IoStatus.__bindgen_anon_1.Status = status;
(*irp).IoStatus.Information = 0;
IofCompleteRequest(irp, 0);
status
}
Вот и всё!
Последним делом осталось подписать драйвер. Похоже, что крейты поддерживают подписание драйверов, если присутствуют файлы INF или INX, но этот драйвер не использует INF. Значит, нужно перед развёртыванием подписать его вручную. Для этого выполните в корне проекта следующую команду:
signtool sign /n wdk /fd sha256 target\debug\booster.dll
Параметр
/n wdk
определяет использование тестового сертификата WDK, обычно создаваемого автоматически Visual Studio при сборке драйверов. Я просто беру из хранилища первый, начинающийся с «wdk» и использую его.Самая нелепая загвоздка здесь в расширении файла — это DLL, и на данный момент нет способа его автоматического изменения с помощью
cargo
в процессе сборки. В случае использования INF/INX это расширение меняется на SYS. Как бы то ни было, расширения не столь значимы — вполне можно оставлять DLL, ну или менять вручную.▍ Установка драйвера
Созданный драйвер можно установить стандартным способом, например, использовать инструмент
sc.exe
(из командной строки с правами администратора) на машине, где включены тестовые подписи. После этого загрузить драйвер в систему можно будет с помощью sc start
:sc.exe sc create booster type= kernel binPath= c:\path_to_driver_file
sc.exe start booster
▍ Тестирование драйвера
Для тестов я использовал существующее приложение на C++, которое взаимодействует с драйвером и ожидает передачи его корректной структуры:
#include <Windows.h>
#include <stdio.h>
struct ThreadData {
int ThreadId;
int Priority;
};
int main(int argc, const char* argv[]) {
if (argc < 3) {
printf("Usage: boost <tid> <priority>\n");
return 0;
}
int tid = atoi(argv[1]);
int priority = atoi(argv[2]);
HANDLE hDevice = CreateFile(L"\\\\.\\Booster",
GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0,
nullptr);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed in CreateFile: %u\n", GetLastError());
return 1;
}
ThreadData data;
data.ThreadId = tid;
data.Priority = priority;
DWORD ret;
if (WriteFile(hDevice, &data, sizeof(data),
&ret, nullptr))
printf("Success!!\n");
else
printf("Error (%u)\n", GetLastError());
CloseHandle(hDevice);
return 0;
}
А вот результат при изменении приоритета потока на 26 (ID 9408):

▍ Заключение
Писать драйверы ядра на Rust можно, и я уверен, что эта возможность будет активно развиваться. Крейты WDK имеют версию 0.3, то есть впереди ещё много работы. Чтобы получить от Rust максимум в этом контексте, нужно создавать безопасные обёртки, которые сделают код менее громоздким, исключат
unsafe
блоки и обеспечат характерные для Rust преимущества. Заметьте, что в этой простой реализации я мог какие-то обёртки упустить. Вот ссылка, где можно найти ещё пару примеров драйверов KMDF, написанных на Rust.
А так весь код статьи лежит на GitHub.
Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (30)
VoodooCat
16.02.2025 09:39Статья безусловно интересная, но как и всё про Rust - опять упомянуто про мнимую безопасность. Драйвера это то, где безопасность компилятора раст вообще ничего не дает. Вы в общем случаете владеете ресурсами и время необходимого владения ими - не определяется кодом вообще. Ну да ладно. Можно только порадоваться за то что экосистема развивается и благодаря, но параллельно лечатся вещи совсем не связанные с растом. )
NN1
16.02.2025 09:39Как сказать не помогает. Для чего у нас есть SAL? Как раз потому, что С ничем не помогает нам определить когда можно что вызывать.
В Rust что-то из этого можно определить в сигнатуре функций вместо отдельного анализа, что конечно приятнее для разработки и подсказок ошибок.
Я не уверен если уже проведена была работа в этом направлении.
https://www.osr.com/blog/2015/02/23/sal-annotations-dont-hate-im-beautiful/
VoodooCat
16.02.2025 09:39В моем комментарии же не было претензии к языку. В C++ нынче тоже есть много интересных аннотаций и статических анализаторов. Мой комментарий был, что вроде статья, вроде вот человек от себя говорит вот так и так, Раст все равно надо знать - и между этим на правах тупой рекламы в самом начале делает вброс про безопасность, как то делалась агрессивно проплаченными "блоггерами" предыдущее десятилетие. Однако по ходу статьи нигде ничего не было упомянуто.
Ну вот сейчас драйвера амд видео (около нг перепроверял) зомбируют хэндл любого запущенного процесса в системе. Поможет ли магия Раст избавиться от таких сюрпризов? Я не верю в это.
Yami-no-Ryuu
16.02.2025 09:39Вы сами себе противоречите.
АМД - вполне возможно, если бы их драйвер был написан на Руст.
И вообще, если ваш драйвер имеет хоть сколько-то нетривиальную логику, то почему бы и не Руст. И да, поскольку это скорее всего будет вещь в себе будете меньше страдать от разнообразия дизайна АПИ в крейтах.
mayorovp
16.02.2025 09:39Вы в общем случаете владеете ресурсами и время необходимого владения ими - не определяется кодом вообще.
Вот именно потому тут Rust и подходит лучше чем плюсы. Потому что в плюсах всё безопасное владение ресурсами сводится к shared_ptr, а в Rust можно выразить правила передачи ссылок.
Yami-no-Ryuu
16.02.2025 09:39Руст можно сказать в первый раз вижу (когда-то смотрел идейно близкий язык Disciple, Хаскель с эффектами и временами жизни).
А нужно ли передавать объекты как *mut, имхо нарушение контракта же.
Или крейт wdk был получен автоматически кодогенератором и сигнатуры никто особо не анализировал?
unreal_undead2
16.02.2025 09:39Получается, что винда демократичнее линукса - можно просто писать драйвера на Rust без всякой нетехнической фигни.
dv0ich
16.02.2025 09:39Получалось бы, если бы статья была про "Пишем простой драйвер на Rust и добиваемся его включения в ядро Windows". А локально делать что угодно с ядром вы можете без всякой бюрократии.
unreal_undead2
16.02.2025 09:39На винде работает без модификации ядра.
Насколько понимаю, основные проблемы на линуксе вызывали вопросы дальнейшей поддержки интерфейсов для драйверов на Rust - поскольку даже совместимость по исходникам для драйверов не особо гарантируется, не говоря уж о бинарной.
withkittens
16.02.2025 09:39Самописное работает только в режиме test-signed code, который никто ради вашего драйвера включать не будет.
qwerty19106
16.02.2025 09:39Основные проблемы на линуксе возникли из-за отсутствия документации интерфейсов на С, и отказа некоторых разработчиков этих интерфейсов их документировать. Это на хабре уже раз 5 разжевывали.
А с бинарной совместимостью нет никаких проблем, на внешние интерфейсы включаем extern "C", точно так же как и в любых других языках.
unreal_undead2
16.02.2025 09:39на внешние интерфейсы включаем extern "C"
Как это поможет в компиляции драйвера при изменении сишных интерфейсов?
qwerty19106
16.02.2025 09:39А как это поможет в компиляции драйвера на С при изменении сишных интерфейсов?
unreal_undead2
16.02.2025 09:39Никак. Но люди, которые корёжат базовый код и меняют интерфейсы, согласны пофиксить при изменениях сишные драйвера, но на Rust даже смотреть не хотят. А основная проблема в том, что стабильные интерфейсы драйверов (желательно - позволяющие грузить старый бинарный драйвер под новое ядро) в линуксе пока не появились.
qwerty19106
16.02.2025 09:39Они может и согласны, но их туда никто не пустит, т.к. обычно это зона ответственности других людей, и делается уже отдельным патчем от них.
Соответственно на всё огромное ядро проблема вылезла ровно в том месте, где один человек написал и внутренние интерфейсы, и сами драйвера, и ничего из этого не задокументировал. И он же пишет что не будет документировать чтобы не пустить в этот код других разработчиков на С в том числе.
Так может дело не в языке программирования, а в синдроме вахтера?
unreal_undead2
16.02.2025 09:39Вроде проблема была с файловыми системами, которые девелопит куча людей.
qwerty19106
16.02.2025 09:39Проблема, о которой я знаю, связана с кодом, который девелопит один конкретный человек.
А там где куча людей, очевидно нужно разделять ответственность, а значит и документировать интерфейсы.
unreal_undead2
16.02.2025 09:39
qwerty19106
16.02.2025 09:39Причем тут репозитарий на Github, я думал мы обсуждаем репозитарий linux. И я не говорил про все файловые системы, не надо мне приписывать.
Я про конкретный кусок кода, который на докладе предложил отрефакторить разработчик Rust. Tso с самим рефакторингом был согласен, но против принципа разделения ответственности (когда каждый отвечает за код на своем языке).
Кроме того он почему-то был против документировать текущее внутреннее API, что очевидно мешает и той куче людей, про которых вы пишите.
AlexRihter9690
16.02.2025 09:39Иногда возникает мимолётная мысль начать раст ботать, но как только вижу такие статьи и этот синтаксис, всё желание сразу пропадает. Буду дальше сидеть на си без std/crt0
Apoheliy
16.02.2025 09:39Судя по статье, у чистого C есть фундаментальное преимущество перед Rust: он работает с теми типами данных, которые ему дали (и неважно, кто дал: железо, операционная система или ещё кто). Вот есть строки как UNICODE_STRING - используем такие строки.
Почему это преимущество: любые преобразования строк (обычно это сущность нефиксированного размера) это потенциальные проблемы по памяти (или, возможно, в Rust строки не отъедают динамическую память?). Возможно ли в Rust использовать те типы данных, которые даны, а не натягивать "сову на глобус"?
Если говорить более общо, то чистый C это язык, в котором можно обойтись без неявного выделения памяти (не только строки, но и массивы и т.д.). Это очень помогает при написании драйверов, модулей в BIOS и др. подобных вещей. Если Rust так умеет - то это очень большой плюс.
qwerty19106
16.02.2025 09:39в котором можно обойтись без неявного выделения памяти (не только строки, но и массивы и т.д.).
В Rust есть ключевой крейт heapless, которым пользуются во всех
no_std
программах (драйвера, ядро, embedded). И в данном случаеalloc
и куча вообще были не нужны. Их автор притянул за уши скорее чтобы показать "смотри как я могу без стандартной библиотеки".Возможно ли в Rust использовать те типы данных, которые даны, а не натягивать "сову на глобус"?
В С и других языках те же проблемы: либо конвертируем utf-8 в utf-16, либо делаем функцию (семейство функций) вывода в лог, которая умеет все кодировки. Если в языке этого сходу не видно, то оно делается неявно (что еще хуже) внутри стандартной библиотеки.
или, возможно, в Rust строки не отъедают динамическую память?
std::String
в куче, но если меньше N байт, то на стеке.heapless:String
всегда на стеке, и есть много других реализаций.Apoheliy
16.02.2025 09:39Возможно, мы про разные вещи говорим ?:
В Rust есть ключевой крейт heapless, которым
Почитал про этот крейт, там говорится про фиксированный максимальный размер. Это немного не то. В чистом C динамическая память используется, но её использование "явное": хочешь кусок памяти большей длины - вот тебе условный realloc, вызываешь его явно. И если тебе нельзя делать realloc (например, в обработчике прерывания это может вызвать глобальный лок) - то ты явно это и не делаешь (а делаешь это в другом месте, попозже). В условном C++ всё это менее явно: можно, конечно, контролировать размеры перед каждым добавлением, но это "кривенько". Вопрос был про Rust: он ближе к чистому C или к C++?
В С и других языках те же проблемы: либо конвертируем utf-8 в utf-16, либо
... либо по-другому: используем те строки, которые выданы. Например, в модулях bios (uefi application) где-то используется CHAR8, где-то CHAR16. И код пишется где-то под одни символы, где-то под другие. Да, неуниверсально. Но меньше накладных расходов и ошибок. Опять же вопрос: а что Rust?: Разные строки, разные типы, работаем без конвертаций?
heapless:String всегда на стеке
такое решение может быть немного узким: всё-таки стек валиден только в самой функции и вложенных, вышел из функции и "карета превратилась в тыкву". Также изменение размера контейнера на стеке ... эм-м-м, конечно, можно всё ... но лучше не надо.
qwerty19106
16.02.2025 09:39Вопрос был про Rust: он ближе к чистому C или к C++?
Стандартные строки std::String ближе к С++, std::OsString и heapless::String ближе к С.
И если тебе нельзя делать realloc
Можно обернуть в Pin, и компилятор запретит делать realloc, и даже move-семантику.
... либо по-другому: используем те строки, которые выданы. Например, в модулях bios (uefi application) где-то используется CHAR8, где-то CHAR16. И код пишется где-то под одни символы, где-то под другие. Да, неуниверсально. Но меньше накладных расходов и ошибок. Опять же вопрос: а что Rust?: Разные строки, разные типы, работаем без конвертаций?
Стандартный и удобный тип один - это std::String. Но библиотеками с const методами (аналог const-expr из С++) можно конвертировать в любую кодировку. В результате нет накладных расходов, и довольно удобно.
Я этим пользуюсь вообще на микроконтроллерах, где нужно передавать в датчики ASCII строки (и иногда старые кодировки, типа latin-1). Получается примерно такой код:
let str_bytes: &'static [u8] = (const { "abcdefg".into_ascii() }).to_bytes();
В этой строке нет копирования байт, она превращается просто в указатель на flash память.
такое решение может быть немного узким: всё-таки стек валиден только в самой функции и вложенных, вышел из функции и "карета превратилась в тыкву".
Любую строку (и любой другой тип), которая лежит на стеке, можно явно переместить в кучу через alloc::Box (std::Box). Это зависит от задачи, но во многих случаях достаточно стека, т.к. есть еще return impl Trait.
Кроме того в случае ошибки код не упадет с сегфолтом как в С, а просто не скомпилируется.
mayorovp
16.02.2025 09:39Судя по статье, у чистого C есть фундаментальное преимущество перед Rust: он работает с теми типами данных, которые ему дали (и неважно, кто дал: железо, операционная система или ещё кто). Вот есть строки как UNICODE_STRING - используем такие строки.
А на Rust что вам мешает делать так же?
IGR2014
16.02.2025 09:39Хотите - минусуйте, но я обязан пошутить:
Когда не взяли в ядро Linux, ба-дум-тсс
Dhwtj
Изменить приоритет можно и через публичное апи. Тут бы задачу которая не решается иначе чем через драйвер.