La conversión de tipos en Cairo es el proceso de convertir valores de un tipo de datos a otro.
Esto se vuelve necesario al trabajar con el estricto sistema de tipos de Cairo, donde se requiere una coincidencia de tipos explícita para las llamadas a funciones, asignaciones de variables, interacciones de contratos y operaciones de datos.
Cairo adopta un enfoque más cauteloso para manejar las conversiones de tipos que Solidity. Proporciona formas claras de manejar los casos en los que la conversión podría fallar, en lugar de permitir conversiones que pueden causar errores no deseados de forma silenciosa, como el truncamiento de datos.
Para ilustrar la diferencia de enfoque entre ambos lenguajes, considere convertir un tipo uint32 a un tipo uint8:
Ejemplo en Solidity:
contract Example {
function castingExample() public pure returns (uint8) {
uint32 largeNumber = 1000;
uint8 smallNumber = uint8(largeNumber);
return smallNumber;
}
}
En Solidity, la conversión trunca silenciosamente largeNumber (1000) a smallNumber (232) utilizando aritmética modular (1000 % 2**8 = 232). La función castingExample() se ejecuta con éxito y devuelve 232 sin ninguna advertencia de que ocurrió una pérdida de datos.
Ejemplo en Cairo:
fn casting_example() -> u8 {
let large_number: u32 = 1000;
let small_number: u8 = large_number.try_into().unwrap();
println!("Small number is : {}", small_number);
small_number
}
fn main(){
casting_example();
}
En tiempo de ejecución, try_into() comprueba si large_number (1000) puede caber en un u8 (valor máximo 255). Cuando la conversión falla, devuelve None, y la llamada posterior a unwrap() entra en pánico con "Option::unwrap failed.", deteniendo inmediatamente la ejecución en lugar de permitir la corrupción de datos.
La sintaxis try_into().unwrap() se cubre en detalle en la siguiente sección.
Diferencia entre conversiones garantizadas y conversiones que podrían fallar
Cairo maneja la conversión de tipos a través de dos traits principales: Into para conversiones seguras y TryInto para conversiones que podrían fallar.
Trait Into: Conversión de tipos infalible
El trait Into se utiliza para conversiones que tienen el éxito garantizado. Estas son conversiones “seguras” donde el tipo de destino siempre puede representar cualquier valor del tipo de origen. Este trait está disponible automáticamente en todos los programas de Cairo sin necesidad de una declaración de importación.
A continuación se muestra un ejemplo que ilustra las conversiones de tipos de enteros más pequeños a más grandes, y de enteros al tipo nativo felt252 de Cairo:
fn main() {
let small_number: u8 = 100;
let large_number: u32 = small_number.into();
let num: u16 = 500;
let as_felt: felt252 = num.into();
}
Aquí está claro que small_number de tipo u8 o cualquier tipo más pequeño que u32 puede caber en larger_number, y felt252 puede contener cualquier valor de todos los tipos uint excepto u256 (que es más grande que felt252).
Cuando usamos .into(), le estamos diciendo al compilador de Cairo: “sabemos que esta conversión siempre funcionará, así que simplemente hazla”. En los ejemplos anteriores, la conversión de small_number de u8 a u32 y num de u16 a felt252 está garantizada para tener éxito porque los tipos de destino pueden contener cualquier valor de los tipos de origen.
Sin embargo, si intentamos usar .into() para convertir de un tipo más grande a un tipo más pequeño, el compilador de Cairo arrojará un error:
fn main() {
let large_number: u256 = 100;
let small_number: u32 = large_number.into(); // ERROR: Trait has no implementation in context: core::traits::Into::<core::integer::u256, core::integer::u32>
}
Este error ocurre porque el trait Into de Cairo solo está implementado para conversiones seguras donde el tipo de destino puede contener todos los valores posibles del tipo de origen. Dado que u256 tiene un rango de valores mucho mayor que u32, no existe una implementación de Into para esta conversión, a pesar de que el valor 100 cabría en un u32.
Trait TryInto: Conversión de tipos falible
El trait TryInto es el método correcto a utilizar para conversiones que podrían fallar si el rango de valores de origen no cabe en el tipo de destino. Al igual que Into, este trait también está disponible automáticamente sin necesidad de una declaración de importación.
TryInto devuelve un enum Option, que puede ser Some(converted_value) si la conversión fue exitosa, o None si falló.
Basándonos en el ejemplo anterior donde 1000 (u32) no podía convertirse de forma segura a u8, la función try_convert_to_u8 a continuación muestra el enfoque de Cairo para las conversiones de tipos potencialmente inseguras. Esta función toma un valor u32 e intenta convertirlo a u8. El código muestra tanto las conversiones exitosas (cuando los valores caben) como las conversiones fallidas (cuando los valores son demasiado grandes para el rango de 0-255 de u8):
fn try_convert_to_u8(num: u32) {
// Attempt to convert u32 to u8 (returns Option<u8>)
let result: Option<u8> = num.try_into();
// Use 'match' to handle both success and failure cases
// 'match' is Cairo's pattern matching (like a switch statement that checks what's inside Option)
match result {
Option::Some(val) => {
// Conversion succeeded (val contains the converted u8 value)
println!("Successfully converted {} to u8: {}", num, val);
},
Option::None => {
// Conversion failed (number was too large for u8 (which holds 0-255))
println!("Conversion failed! {} is too big for u8 (max: 255)", num);
}
}
}
fn main() {
try_convert_to_u8(1000); // Will fail (too large for u8)
try_convert_to_u8(100); // Will succeed (fits in u8)
try_convert_to_u8(255); // Will succeed (maximum u8 value)
try_convert_to_u8(256); // Will fail (exceeds u8 maximum by 1)
}
Dentro de la función try_convert_to_u8, llamamos a try_into() sobre el valor de entrada num, lo que devuelve un tipo Option que almacenamos en result
La declaración match es la característica de coincidencia de patrones de Cairo, similar a una declaración switch en otros lenguajes. Comprueba qué hay dentro del Option y ejecuta un bloque de código específico dependiendo de si el resultado tiene un valor o está vacío. Si la conversión tuvo éxito, obtenemos Some(val) que contiene el valor convertido, e imprimimos un mensaje de éxito. Si la conversión falló porque el número era demasiado grande, obtenemos None, e imprimimos un mensaje de error explicando el fallo.
En la función main(), probamos cuatro escenarios diferentes para demostrar cómo se comporta la conversión con varios valores de entrada.
Cuando llamamos a try_convert_to_u8(1000), el diagrama a continuación muestra cómo la conversión devuelve None en Option<u8> porque 1000 excede el valor máximo de u8 (255):

Dado que la conversión devolvió None, la declaración match detecta que está vacía y ejecuta la rama Option::None, imprimiendo el mensaje de error "Conversion failed! 1000 is too big for u8 (max: 255).”
Posteriormente, cuando se ejecuta try_convert_to_u8(100), el diagrama a continuación muestra cómo la conversión devuelve Some(100) en Option<u8> porque 100 cabe dentro del rango válido de 0-255:

Dado que la conversión tuvo éxito, la declaración match ejecuta la rama Option::Some(val), imprimiendo “Successfully converted 100 to u8: 100.”
Cuándo usar into() o try_into()
Utilice into() para:
- Convertir tipos más pequeños a más grandes (por ejemplo, de
u32au64) - Cualquier conversión donde la pérdida de datos sea imposible
into()no permite explícitamente la conversión de tipos más grandes a más pequeños, incluso si el valor específico cabría en el rango de destino.
Utilice try_into() para:
- Convertir tipos más grandes a más pequeños (de
u32au8) - Cualquier conversión donde el valor podría no caber
- Cuando desee manejar los fallos de conversión de manera controlada en lugar de entrar en pánico, use
try_into()conmatch
Con una comprensión de los mecanismos de conversión de Cairo, ahora podemos analizar cómo se aplican estos traits a escenarios de conversión específicos.
felt252 a uints
Convertir de felt252 a tipos de enteros sin signo es una operación común en Cairo, ya que felt252 es el tipo nativo. El enfoque de conversión depende de si estamos convirtiendo a un tipo de entero más grande o más pequeño.
Aquí hay un ejemplo que convierte un valor felt252 (felt_value) a u256 (as_u256):
fn main() {
let felt_value: felt252 = 42615;
let as_u256: u256 = felt_value.into();
}
Se garantiza que esta conversión tendrá éxito, ya que u256 puede contener cualquier valor de felt252, razón por la cual podemos usar .into() en lugar de .try_into().
Convertir felt252 a tipos de enteros más pequeños requiere try_into(), ya que el valor de felt252 podría exceder el rango del tipo de destino.
Esta función intenta convertir felt252 a u8, pero entrará en pánico si el valor es mayor que 255:
fn convert_felt_to_small_uint(felt_value: felt252) -> u8 {
felt_value.try_into().unwrap()
}
La función convert_felt_to_small_uint toma un valor felt252, intenta convertirlo a u8 y entra en pánico si falla. Utiliza .unwrap() para extraer el valor u8 del Option devuelto por try_into(). Si el valor felt252 excede 255, la conversión devuelve None y .unwrap() hará que el programa entre en pánico.
Una forma más segura es manejar los posibles fallos de conversión de manera explícita sin entrar en pánico. El siguiente código muestra el manejo adecuado de errores mediante la creación de una función que devuelve Option<u8> en lugar de entrar en pánico, y luego prueba tanto una conversión exitosa (100) como una conversión fallida (1000), con declaraciones match para manejar cada resultado de forma apropiada:
fn safe_convert_felt_to_u8(felt_value: felt252) -> Option<u8> {
felt_value.try_into()
}
fn main() {
let small_felt: felt252 = 100;
let large_felt: felt252 = 1000;
let small_as_u8 = safe_convert_felt_to_u8(small_felt); // Returns Some(100)
println!("Small conversion result: {:?}", small_as_u8);
let large_as_u8 = safe_convert_felt_to_u8(large_felt); // Returns None
// handle the successful conversion
match small_as_u8 {
Option::Some(val) => println!("Successfully converted 100 to u8: {}", val),
Option::None => println!("Small conversion failed"),
}
// handle the failed conversion
match large_as_u8 {
Option::Some(val) => println!("Converted: {}", val),
Option::None => println!("Conversion failed: 1000 is too large for u8"),
}
}
safe_convert_felt_to_u8 toma un valor felt252 y devuelve Option<u8>. Note que no usa .unwrap(); devuelve el Option directamente desde try_into(), permitiendo que quien la llama decida cómo manejar los posibles fallos.
En la función main, probamos dos escenarios:
- Convertir 100 a
u8: Esto tiene éxito porque 100 cabe dentro del rango deu8(0-255), por lo quesmall_as_u8contieneSome(100) - Convertir 1000 a
u8: Esto falla porque 1000 excede el valor máximo deu8de 255, por lo quelarge_as_u8contieneNone
La primera declaración match maneja la conversión exitosa. Dado que small_as_u8 contiene Some(100), coincide con la rama Option::Some(val) e imprime el mensaje de éxito con el valor convertido.
La segunda declaración match maneja la conversión fallida. Dado que large_as_u8 contiene None, coincide con la rama Option::None e imprime un mensaje de error explicando por qué falló la conversión.
Esto muestra cómo manejar de manera controlada tanto las conversiones exitosas como las fallidas sin entrar en pánico, dándonos control total sobre el manejo de errores en nuestro programa.
uints a tipo Address
Cairo no permite la conversión directa de tipos de enteros a direcciones. En cambio, las conversiones deben pasar por felt252 como un tipo intermedio. La conversión de uints a direcciones se vuelve necesaria al trabajar con IDs de usuario, identificadores numéricos, o al derivar direcciones a partir de cálculos con enteros en los smart contracts.
El proceso de conversión consta de dos pasos: primero convertir el entero a felt252, y luego convertir el felt252 a ContractAddress:
use starknet::ContractAddress;
fn user_address(user_id: u64) -> ContractAddress {
let address_felt: felt252 = user_id.into();
address_felt.try_into().unwrap()
}
La función user_address acepta un parámetro u64 llamado user_id. Primero convierte el valor u64 a felt252 utilizando .into(), lo cual siempre tiene éxito porque felt252 puede contener cualquier valor u64 y lo almacena en address_felt. Luego convierte el felt252 de address_felt a ContractAddress usando .try_into().unwrap().
Usamos .try_into() porque es el único método de conversión disponible de felt252 a ContractAddress. El .unwrap() extrae el resultado pero entrará en pánico si la conversión falla.
En el ejemplo de código anterior, el método try_into() devuelve Option<ContractAddress>; ya sea Some(address) si la conversión tiene éxito o None si falla. El método .unwrap() extrae el valor ContractAddress real del Some(), pero entrará en pánico si la conversión falló y devolvió None.
Convertir u256 requiere precaución adicional ya que los valores de u256 pueden exceder el rango de felt252, y ContractAddress tiene un rango válido aún más pequeño de [0, 2**251). Esto significa que ambos pasos de conversión pueden fallar.
Para los casos en los que estamos seguros de que el valor cabrá, por ejemplo, cuando el valor está dentro del rango de felt252, podemos usar el enfoque directo:
fn convert_u256_to_address(value: u256) -> ContractAddress {
// First step: convert u256 to felt252 (will panic if value exceeds felt252 range)
let address_felt: felt252 = value.try_into().unwrap();
// Second step: convert felt252 to ContractAddress (will panic if outside valid address range)
address_felt.try_into().unwrap()
}
Al tratar con valores u256 arbitrarios que podrían estar fuera del rango válido, es mejor manejar los posibles fallos de forma explícita:
fn safe_convert_u256_to_address(value: u256) -> Option<ContractAddress> {
// First step: try to convert u256 to felt252 (might fail if value is too large)
match value.try_into() {
Option::Some(felt_val) => {
// u256 to felt252 conversion succeeded
let address_felt: felt252 = felt_val;
// Second step: try to convert felt252 to ContractAddress
// This can also fail if the felt252 value is outside valid address range
address_felt.try_into()
}
Option::None => {
// u256 to felt252 conversion failed (value too large for felt252)
Option::None
}
}
}
En lugar de entrar en pánico por un fallo en la conversión, safe_convert_u256_to_address devuelve None para entradas que exceden el rango de ContractAddress, lo que permite que quien la llama maneje las entradas de gran tamaño de forma controlada.
Conversión de ByteArray y String
La conversión entre representaciones de cadenas y tipos numéricos en Cairo implica trabajar con los tipos de cadenas de Cairo: cadenas cortas (que son felt252) y ByteArray para cadenas más largas.
Cadenas cortas
fn main() {
let string_as_felt: felt252 = 'Hello';
let hex_representation: felt252 = 0x48656c6c6f;
println!("String: {}", string_as_felt);
println!("Hex form: {}", hex_representation);
}
Cuando se ejecute este código, verá que las cadenas cortas en Cairo se almacenan directamente como valores felt252.

No ocurre ninguna conversión; 'Hello' y su equivalente hexadecimal 0x48656c6c6f son el mismo valor felt252.
Cadenas cortas a uints
A continuación, un ejemplo que convierte un carácter (almacenado como felt252) a su valor ASCII como un u8:
fn main() {
let char_a: felt252 = 'A';
let char_as_u8: u8 = char_a.try_into().unwrap();
println!("Character 'A' as u8: {}", char_as_u8);
}
Cuando se ejecuta este código, muestra “Character ‘A’ as u8: 65” en la terminal porque el valor del carácter cabe en el tipo de destino. Si el valor no cabe, el programa entrará en pánico, por lo que es importante manejar los casos en los que no se tiene certeza de si la conversión será exitosa.
Operaciones con ByteArray
Para convertir datos de tipo ByteArray, se accede a los bytes individuales mediante la indexación y cada byte se convierte por separado.
El tipo ByteArray es principalmente para manejar cadenas de más de 31 bytes, no para conversiones de tipos numéricos.
Tenga en cuenta que al acceder a bytes individuales en un ByteArray se devuelve un u8. Cuando es necesario trabajar con caracteres individuales de un ByteArray como valores numéricos, se pueden extraer por índice y convertir a otros tipos numéricos como u32 o felt252. Considere el siguiente ejemplo:
fn main() {
let text: ByteArray = "Cairo";
let first_byte = text[0];
let third_byte = text[2];
let byte_as_u32: u32 = first_byte.into();
let byte_as_felt: felt252 = third_byte.into();
println!("First byte 'C' as u32: {}", byte_as_u32);
println!("Third byte 'i' as felt252: {}", byte_as_felt);
}
Se accede al ByteArray “Cairo” donde el primer carácter, ‘C’, se obtiene usando text[0], lo que devuelve un valor u8. Luego se convierte a u32 utilizando .into(), lo que garantiza el éxito ya que u32 puede contener cualquier valor u8. De manera similar, el tercer carácter ‘i’ en text[2] se convierte a felt252.
De esta manera, trabajamos con el contenido del ByteArray a nivel de bytes y convertimos caracteres individuales a los tipos numéricos que necesite.
La mayoría de las veces, si tenemos un ByteArray, probablemente querremos mantenerlo como datos de cadena ya que ByteArray está optimizado para almacenar y manipular texto en lugar de realizar cálculos numéricos.
Conversión de bool
Cairo mantiene los valores booleanos (true/false) separados de los tipos numéricos por diseño, pero permite convertir valores booleanos a felt252 usando .into(), donde true se convierte en 1 y false en 0. Sin embargo, los booleanos no se pueden convertir directamente a tipos de enteros como u32, u64, etc., mediante métodos de conversión automática:
fn main() {
let flag: bool = true;
// This works, bool to felt252:
let as_felt: felt252 = flag.into(); // Works: true becomes 1, false becomes 0
// This will cause a compilation error:
// let as_u32: u32 = flag.into(); // ERROR: Trait has no implementation in context: core::traits::Into::<core::bool, core::integer::u32>.
// Manual conversion for integers:
let as_u32: u32 = if flag {
1
} else {
0
};
}
Los números no se pueden convertir de nuevo a booleanos automáticamente. Se requieren comparaciones explícitas como se muestra a continuación:
fn main() {
let number: u32 = 1;
let felt_num: felt252 = 1;
// These will cause compilation errors:
// let back_to_bool: bool = number.into(); // ERROR: Trait has no implementation in context: core::traits::Into::<core::integer::u32, core::bool>.
// let felt_to_bool: bool = felt_num.into(); // ERROR: Trait has no implementation in context: core::traits::Into::<core::felt252, core::bool>.
// Manual conversion:
let back_to_bool: bool = number != 0;
let felt_to_bool: bool = felt_num != 0;
}
Los booleanos tampoco se pueden utilizar directamente como índices de arrays ni en operaciones aritméticas:
fn main() {
let my_array = array![10, 20];
let index: bool = true;
// These will cause compilation errors:
// let value = my_array[index];
// let result = index + 1;
// Conversions work:
let numeric_index: u32 = if index {
1
} else {
0
};
let value = my_array[numeric_index];
}
Así que el punto clave es: la conversión de bool a felt252 funciona automáticamente, pero de bool a tipos de enteros requiere conversión manual, y ninguna conversión automática funciona en la dirección inversa.
En la mayoría de los casos, los booleanos deben seguir siendo booleanos para una mejor seguridad de tipos y claridad del código.
Conclusión
La conversión de tipos en Cairo enfatiza la seguridad y la claridad explícita sobre la conversión automática. Utiliza Into para conversiones garantizadas y TryInto para conversiones que podrían fallar, lo que obliga a manejar los posibles errores de forma explícita.
Este enfoque previene la pérdida silenciosa de datos, común en otros lenguajes, y detecta errores antes de que lleguen a producción. Las conversiones explícitas de Cairo significan que sus smart contracts se comportan de manera predecible, incluso al manejar datos inesperados.
A diferencia de Solidity, que requiere controles de seguridad manuales para la conversión de tipos, Cairo integra el manejo de errores directamente en su sistema de conversión mediante los tipos Option.
Si bien los requisitos de conversión de Cairo necesitan más código que la conversión implícita, esta naturaleza explícita construye smart contracts más confiables.
Este artículo es parte de una serie de tutoriales sobre Cairo Programming on Starknet