При передаче текста из Rust в C какой инвариант требуется сохранить, чтобы чтение до нулевого байта не вышло за границы объекта?
Указатель должен обозначать действительную последовательность инициализированных байтов, внутри доступной памяти заканчивающуюся нулевым байтом. Эта память должна оставаться выделенной и неизменной настолько долго, насколько C-код может читать указатель.
Для обычной Rust-строки UTF-8 сам по себе такой инвариант не гарантирует: Rust хранит длину отдельно, а C-функция часто ищет конец по нулевому байту. CString создаёт подходящее представление, отклоняя внутренние нулевые байты и добавляя завершающий нулевой байт.
Классический интерфейс C передаёт строку как указатель на первый байт и не передаёт её длину. Поэтому вызывающая или вызываемая сторона должна найти конец последовательности по специальному нулевому байту.
В Rust строки и срезы устроены иначе: их длина хранится явно, а наличие нулевого байта внутри текста допустимо. Типы CString и CStr отделяют представление, пригодное для C, от обычных Rust-строк и делают соответствующий контракт явным.
Если завершающего нулевого байта нет в доступной памяти, C-функция продолжит чтение за пределами объекта. Это может привести к неопределённому поведению, утечке содержимого соседней памяти или аварийному завершению.
Внутренний нулевой байт создаёт другую проблему: C обычно воспримет его как конец строки и обработает только префикс. Если указатель используется после уничтожения исходного объекта, он становится висячим независимо от того, был ли терминатор сформирован корректно.
CString гарантирует отсутствие внутренних нулевых байтов и добавляет один нулевой байт в конце. CStr представляет уже существующую C-строку и при создании из raw pointer требует, чтобы указатель был ненулевым, адресовал доступную память с корректными байтами и приводил к нулевому байту в пределах допустимого объекта.
CString::as_ptr не передаёт владение памятью: объект text должен жить всё время синхронного вызова. Если C-библиотека сохраняет указатель, одного времени жизни локальной переменной недостаточно — нужно явно определить модель владения и освобождения.
Ручное создание буфера с нулевым байтом возможно, но требует самостоятельно доказать отсутствие внутренних нулей, корректность терминатора, инициализацию и время жизни. Передача пары указатель-длина безопаснее для API, который её поддерживает, но не заменяет C-строку для функции, которая принимает только const char* и ищет терминатор.
Библиотека журналирования C принимает const char* и синхронно читает строку до нулевого байта. Рассматривались три варианта: передать указатель на внутренний буфер Rust-строки, вручную скопировать байты в Vec<u8> с терминатором или создать CString.
Первый вариант не даёт C требуемого строкового представления. Второй может работать, но переносит на разработчика проверку внутренних нулей, стабильности адреса и времени жизни буфера; изменение Vec после получения указателя может сделать указатель недействительным.
Выбран CString: он проверяет отсутствие внутренних нулей, хранит завершающий байт и остаётся живым до конца синхронного вызова. Это уменьшает объём ручного доказательства, а результатом становится корректное чтение всей строки без выхода за границы.
Нет. UTF-8 определяет кодирование последовательности байтов, но не способ определения её длины. C-функции, работающие с нуль-терминированными строками, требуют отдельного завершающего нулевого байта; кроме того, конкретная C-библиотека может ожидать другую кодировку.
C-функция обычно остановится на первом нулевом байте и увидит только префикс. CString::new возвращает ошибку для такого содержимого, что позволяет не скрывать потерю данных; если бинарные данные допускают нули, для них следует использовать интерфейс с явной длиной.
CString::as_ptr после выхода из области видимости CString?Нет, если после этого C-код может читать указатель. Уничтожение CString освобождает принадлежащую ему память, и сохранённый адрес становится висячим. Для долгого хранения нужно передать владение согласованным способом, например через специально оговорённый FFI-контракт, и обеспечить освобождение тем аллокатором и тем API, которые предусмотрены этим контрактом.