La interoperabilidad entre Rust y C presenta un desafío fundamental debido a la representación de las cadenas de texto en memoria. Rust utiliza String y &str, que son buffers de UTF-8 con longitud explícita y no dependen de un terminador para conocer su tamaño. Por el contrario, C utiliza cadenas de caracteres terminadas en un carácter nulo (\0), conocidas como null-terminated strings. Intentar pasar un *const u8 de un &str de Rust directamente a una función de C que espera una cadena de C provocará errores de lectura de memoria, ya que C seguirá leyendo bytes hasta encontrar un \0 inexistente.
Para resolver esto, Rust proporciona dos tipos fundamentales: CString y CStr. CString es una versión “propiedad” (owned) de una cadena de C; es decir, es un String que tiene un byte extra al final conteniendo el terminador nulo. Se utiliza para enviar datos desde Rust hacia C. Es vital notar que CString::new() fallará si la cadena contiene un carácter nulo interno, ya que esto corrompería la estructura de la cadena en C.
CStr es el equivalente a un slice (&str) pero orientado a la interfaz de C. Representa una referencia a una secuencia de bytes terminada en nulo que ya reside en memoria. Se utiliza para recibir o “envolver” punteros que provienen de código C. La conversión de un puntero crudo (*const c_char) a un CStr es una operación unsafe porque Rust no puede garantizar que el puntero sea válido o que realmente termine en un nulo.
Al convertir de CStr a tipos de Rust (&str), existen dos caminos: to_str(), que devuelve un Result porque la cadena proveniente de C podría no ser UTF-8 válida, y to_string_lossy(), que permite recuperar una representación legible sustituyendo bytes inválidos por el carácter de reemplazo �.
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
// Simulamos una función en C que recibe un puntero a caracteres
// En un escenario real, esto estaría en una biblioteca externa (.so o .dll)
extern "C" {
fn c_function_receiver(ptr: *const c_char);
}
// Implementación de la simulación de la función de C en Rust
#[no_mangle]
pub unsafe extern "C" fn c_function_receiver(ptr: *const c_char) {
if ptr.is_null() { return; }
// CStr::from_ptr es unsafe: asumimos que el puntero es válido y termina en \0
let c_str_ref = CStr::from_ptr(ptr);
// Intentamos convertir a &str (UTF-8 seguro)
match c_str_ref.to_str() {
Ok(s) => println!("[C-Receiver] Recibido string UTF-8 válido: {}", s),
Err(_) => println!("[C-Receiver] Error: El puntero no contiene UTF-8 válido"),
}
// Alternativa para datos no confiables: to_string_lossy
let lossy = c_str_ref.to_string_lossy();
println!("[C-Receiver] Representación lossy: {}", lossy);
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Enviar datos de Rust a C usando CString
let mensaje_rust = "Hola desde Rust!";
let c_string = CString::new(mensaje_rust)?;
// .as_ptr() devuelve *const c_char para pasar a C
// IMPORTANTE: c_string debe vivir tanto como la llamada a la función
unsafe {
c_function_receiver(c_string.as_ptr());
}
// 2. Simulación de recibir datos de C (un puntero a bytes que no es UTF-8)
// Creamos un array de bytes con un byte inválido para UTF-8 (0xFF)
let datos_corruptos: [c_char; 5] = [b'H', b'o', b'l', 0xFF, 0];
let ptr_corrupto = datos_corruptos.as_ptr();
println!("\n--- Probando datos no UTF-8 ---");
unsafe {
c_function_receiver(ptr_corrupto);
}
Ok(())
}
Análisis del código
CString::new(mensaje_rust)?: Crea una nueva instancia deCStringque añade el terminador nulo. El operador?gestiona elResulten caso de quemensaje_rustcontenga un nulo interno.c_string.as_ptr(): Este método dec_stringdevuelve un puntero crudo (*const c_char). Es crucial entender que la propiedad (ownership) reside enc_string; sic_stringse liberara antes de la llamada ac_function_receiver, el puntero sería inválido (dangling pointer).CStr::from_ptr(ptr): Dentro de la función simulada, transformamos el puntero crudoptren una referencia segurac_str_refde tipo&CStr. Esta operación esunsafeporque delegamos la responsabilidad de la validez del puntero al programador.c_str_ref.to_str(): Intenta convertir la referencia de C a un&strde Rust. Devuelve unResult<&str, Utf8Error>. En el primer caso demain, funciona porque “Hola desde Rust!” es UTF-8.c_str_ref.to_string_lossy(): En el segundo caso, dondedatos_corruptoscontiene0xFF(inválido en UTF-8),to_str()fallaría, peroto_string_lossy()permite la conversión reemplazando el byte corrupto, garantizando que el programa no colapse al manejar datos externos.#[no_mangle]: Se utiliza en la implementación de la función para evitar que el compilador cambie el nombre de la función, asegurando que sea localizable mediante su nombre exacto durante la simulación de la interoperabilidad.
N° 98