本文详细介绍了 Cairo 中类型转换的概念、Into 和 TryInto 两个 trait 的区别和使用场景,以及 felt252、uint、Address、ByteArray、String 和 bool 等类型之间的转换方法。Cairo 强调类型安全和显式转换,避免了隐式转换可能导致的数据丢失和错误,提高了智能合约的可靠性。
Cairo 中的类型转换是将值从一种数据类型转换为另一种数据类型的过程。
当使用 Cairo 严格的类型系统时,这变得很有必要,在 Cairo 中,函数调用、变量赋值、合约交互以及数据操作都需要显式的类型匹配。
Cairo 采用比 Solidity 更谨慎的方式来处理类型转换。它提供了清晰的方法来处理可能导致转换失败的情况,而不是允许可能静默地导致意外错误(如数据截断)的转换。
为了说明这两种语言在方法上的差异,请考虑将 uint32 类型转换为 uint8 类型:
Solidity 示例:
contract Example {
function castingExample() public pure returns (uint8) {
uint32 largeNumber = 1000;
uint8 smallNumber = uint8(largeNumber);
return smallNumber;
}
}
在 Solidity 中,类型转换使用模运算(1000 % 2**8 = 232)将 largeNumber (1000) 静默截断为 smallNumber (232)。 castingExample() 函数成功执行并返回 232,没有任何数据丢失的警告。
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();
}
在运行时,try_into() 检查 large_number (1000) 是否可以放入 u8(最大值 255)中。当转换失败时,它返回 None,随后的 unwrap() 调用会因 "Option::unwrap failed." 而 panic,立即停止执行,而不是允许数据损坏。
try_into().unwrap() 语法将在下一节中详细介绍。
Cairo 通过两个主要的 trait 处理类型转换:用于安全转换的 Into 和用于可能失败的转换的 TryInto。
Into Trait:绝对安全的类型转换Into trait 用于保证成功的转换。这些都是 “安全” 的转换,其中目标类型总是可以表示源类型的任何值。
这是一个示例,展示了从小整数类型到大整数类型,以及从整数类型到 Cairo 的原生 felt252 类型的转换:
fn main() {
let small_number: u8 = 100;
let large_number: u32 = small_number.into();
let num: u16 = 500;
let as_felt: felt252 = num.into();
}
这里很明显,类型为 u8 的 small_number 或任何小于 u32 的类型都可以放入 larger_number 中,并且 felt252 可以包含除 u256 之外的所有 uint 类型的值(u256 大于 felt252)。
当我们使用 .into() 时,我们是在告诉 Cairo 编译器,“我们知道这种转换总是会成功,所以直接做就好。” 在上面的示例中,将 small_number 从 u8 转换为 u32,以及将 num 从 u16 转换为 felt252 保证会成功,因为目标类型可以容纳源类型中的任何值。
但是,如果我们尝试使用 .into() 从较大的类型转换为较小的类型,Cairo 编译器会抛出一个错误:
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>
}
出现此错误的原因是,Cairo 的 Into trait 仅为安全转换而实现,其中目标类型可以容纳源类型的所有可能值。由于 u256 比 u32 具有更大的值范围,因此即使值 100 可以放入 u32 中,也没有用于此转换的 Into 实现。
TryInto Trait:可能失败的类型转换如果源值范围不适合目标类型,则 TryInto trait 是用于可能失败的转换的正确方法。
TryInto 返回一个 Option 枚举,如果转换成功,则可以是 Some(converted_value),如果转换失败,则可以是 None。
在前面的示例中,1000 (u32) 无法安全地转换为 u8 的基础上,下面的 try_convert_to_u8 函数展示了 Cairo 处理潜在不安全类型转换的方法。此函数接受一个 u32 值并尝试将其转换为 u8。该代码显示了成功的转换(当值适合时)和失败的转换(当值对于 u8 的 0-255 范围来说太大时):
fn try_convert_to_u8(num: u32) {
// Attempt to convert u32 to u8 - returns Option<u8>
// 尝试将 u32 转换为 u8 - 返回 Option<u8>
let result: Option<u8> = num.try_into();
// Use 'match' to handle both success and failure cases
// 使用 'match' 来处理成功和失败的情况
// 'match' is Cairo's pattern matching - like a switch statement that checks what's inside Option
// 'match' 是 Cairo 的模式匹配 - 就像一个检查 Option 内部内容的 switch 语句
match result {
Option::Some(val) => {
// Conversion succeeded - val contains the converted u8 value
// 转换成功 - val 包含转换后的 u8 值
println!("Successfully converted {} to u8: {}", num, val);
},
Option::None => {
// Conversion failed - number was too large for u8 (which holds 0-255)
// 转换失败 - 数字对于 u8 来说太大(u8 保存 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
}
在 try_convert_to_u8 函数内部,我们在输入 num 值上调用 try_into(),它返回一个 Option 类型,我们将其存储在 result 中
match 语句是 Cairo 的模式匹配功能,类似于其他语言中的 switch 语句。它检查 Option 内部的内容,并根据结果是否具有值或为空执行特定的代码块。如果转换成功,我们将获得包含转换值的 Some(val),并打印一条成功消息。如果转换失败,因为数字太大,我们将获得 None,并打印一条错误消息,解释失败的原因。
在 main() 函数中,我们测试四种不同的情况,以演示转换在各种输入值下的行为方式。
当我们调用 try_convert_to_u8(1000) 时,下图显示了转换如何在 Option<u8> 中返回 None,因为 1000 超过了 u8 的最大值 (255):

由于转换返回 None,因此 match 语句检测到它为空并执行 Option::None 分支,打印错误消息 "Conversion failed! 1000 is too big for u8 (max: 255)。”
随后,当 try_convert_to_u8(100) 运行时,下图显示了转换如何在 Option<u8> 中返回 Some(100),因为 100 适合 0-255 的有效范围内:

由于转换成功,因此 match 语句执行 Option::Some(val) 分支,打印 "Successfully converted 100 to u8: 100."。
into() 或 try_into()使用 into() 用于:
即使特定值适合目标范围,
into()也会明确禁止从较大类型转换为较小类型。
使用 try_into() 用于:
try_into() 与 match 一起使用)了解了 Cairo 的类型转换机制后,我们现在可以研究这些 trait 如何应用于特定的转换场景。
felt252 到 uints将 felt252 转换为无符号整数类型是 Cairo 中的常见操作,因为 felt252 是原生类型。转换方法取决于我们是转换为较大还是较小的整数类型。
这是一个将 felt252 值 (felt_value) 转换为 u256 (as_u256) 的示例:
fn main() {
let felt_value: felt252 = 42615;
let as_u256: u256 = felt_value.into();
}
此转换保证成功,因为 u256 可以容纳任何 felt252 值,这就是为什么我们可以使用 .into() 而不是 .try_into()。
将 felt252 转换为较小的整数类型需要 try_into(),因为 felt252 值可能超过目标类型的范围。
此函数尝试将 felt252 转换为 u8,但如果该值大于 255,则会 panic:
fn convert_felt_to_small_uint(felt_value: felt252) -> u8 {
felt_value.try_into().unwrap()
}
convert_felt_to_small_uint 函数接受一个 felt252 值,尝试将其转换为 u8,如果失败则会 panic。它使用 .unwrap() 从 try_into() 返回的 Option 中提取 u8 值。如果 felt252 值超过 255,则转换将返回 None,并且 .unwrap() 将导致程序 panic。
一种更安全的方法是显式处理潜在的转换失败,而不会 panic。以下代码通过创建一个返回 Option<u8> 而不是 panic 的函数来显示正确的错误处理,然后测试成功的转换 (100) 和失败的转换 (1000),并使用 match 语句来适当地处理每种结果:
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 接受一个 felt252 值并返回 Option<u8>。请注意,它不使用 .unwrap();它直接从 try_into() 返回 Option,让调用方决定如何处理潜在的失败。
在 main 函数中,我们测试了两种情况:
u8:此操作成功,因为 100 适合 u8 的范围 (0-255) 内,因此 small_as_u8 包含 Some(100)u8:此操作失败,因为 1000 超过了 u8 的最大值 255,因此 large_as_u8 包含 None第一个 match 语句处理成功的转换。由于 small_as_u8 包含 Some(100),因此它与 Option::Some(val) 分支匹配,并打印带有转换值的成功消息。
第二个 match 语句处理失败的转换。由于 large_as_u8 包含 None,因此它与 Option::None 分支匹配,并打印一条错误消息,解释转换失败的原因。
这展示了如何在不 panic 的情况下优雅地处理成功和失败的转换,从而使我们能够完全控制程序中的错误处理。
Cairo 不允许从整数类型直接转换为地址。相反,转换必须通过 felt252 作为中间类型。当使用用户 ID、数字标识符或在智能合约中从整数计算中派生地址时,将 uints 转换为地址变得很有必要。
转换过程包括两个步骤:首先将整数转换为 felt252,然后将 felt252 转换为 ContractAddress。
use starknet::ContractAddress;
fn user_address(user_id: u64) -> ContractAddress {
let address_felt: felt252 = user_id.into();
address_felt.try_into().unwrap()
}
user_address 接受一个 u64 user_id。它首先使用 .into() 将 u64 值转换为 felt252,这总是会成功,因为 felt252 可以容纳任何 u64 值并将其存储在 address_felt 中。然后,它使用 .try_into().unwrap() 将 felt252 address_felt 转换为 ContractAddress。
我们使用 .try_into(),因为它是从 felt252 到 ContractAddress 的唯一可用转换方法。.unwrap() 提取结果,但如果转换失败则会 panic。
在上面的代码示例中,try_into() 方法返回 Option<ContractAddress>;如果转换成功,则返回 Some(address),如果转换失败,则返回 None。.unwrap() 方法从 Some() 中提取实际的 ContractAddress 值,但如果转换失败并返回 None,则会 panic。
转换 u256 需要格外小心,因为 u256 值可能超过 felt252 范围,并且 ContractAddress 具有更小的有效范围 [0, 2**251)。这意味着两个转换步骤都可能失败。
对于我们确信该值适合的情况,例如当值在 felt252 范围内时,我们可以使用直接方法:
fn convert_u256_to_address(value: u256) -> ContractAddress {
// First step: convert u256 to felt252 - will panic if value exceeds felt252 range
// 第一步:将 u256 转换为 felt252 - 如果值超过 felt252 范围,则会 panic
let address_felt: felt252 = value.try_into().unwrap();
// Second step: convert felt252 to ContractAddress - will panic if outside valid address range
// 第二步:将 felt252 转换为 ContractAddress - 如果超出有效地址范围,则会 panic
address_felt.try_into().unwrap()
}
在处理可能超出有效范围的任意 u256 值时,最好显式处理潜在的失败:
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)
// 第一步:尝试将 u256 转换为 felt252(如果值太大可能会失败)
match value.try_into() {
Option::Some(felt_val) => {
// u256 → felt252 conversion succeeded
// u256 → felt252 转换成功
let address_felt: felt252 = felt_val;
// Second step: try to convert felt252 to ContractAddress
// 第二步:尝试将 felt252 转换为 ContractAddress
// This can also fail if the felt252 value is outside valid address range
// 如果 felt252 值超出有效地址范围,这也会失败
address_felt.try_into()
}
Option::None => {
// u256 → felt252 conversion failed (value too large for felt252)
// u256 → felt252 转换失败(值对于 felt252 来说太大)
Option::None
}
}
}
safe_convert_u256_to_address 不会在转换失败时 panic,而是为超出 ContractAddress 范围的输入返回 None,从而允许调用方优雅地处理过大的输入。
Cairo 中字符串表示形式和数字类型之间的转换涉及使用 Cairo 的字符串类型:短字符串(即 felt252)和用于较长字符串的 ByteArray。
fn main() {
let string_as_felt: felt252 = 'Hello';
let hex_representation: felt252 = 0x48656c6c6f;
println!("String: {}", string_as_felt);
println!("Hex form: {}", hex_representation);
}
运行此代码时,你将看到 Cairo 中的短字符串直接存储为 felt252 值。

没有发生任何转换;'Hello' 及其十六进制等效项 0x48656c6c6f 是相同的 felt252 值。
这是一个将字符(存储为 felt252)转换为其 ASCII 值的 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);
}
运行此代码时,它将在终端中显示 "Character ‘A’ as u8: 65",因为字符值适合目标类型。如果该值不适合,程序将 panic,因此处理转换成功不确定的情况非常重要。
要转换 ByteArray 数据,可以通过索引访问各个字节,并分别转换每个字节。
ByteArray 类型主要用于处理超过 31 个字节的字符串,而不是用于数字类型转换。
请注意,访问 ByteArray 中的各个字节会返回 u8。当需要将 ByteArray 中的各个字符用作数值时,可以通过索引提取它们并将其转换为其他数值类型,如 u32 或 felt252。请考虑以下示例:
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);
}
访问了 ByteArray "Cairo",其中使用 text[0] 访问了第一个字符 'C',该字符返回一个 u8 值。然后使用 .into() 将其转换为 u32,保证会成功,因为 u32 可以容纳任何 u8 值。同样,位于 text[2] 的第三个字符 'i' 也被转换为 felt252。
这样,我们就可以在字节级别处理 ByteArray 内容,并将各个字符转换为所需的数值类型。
在大多数情况下,如果我们有一个 ByteArray,我们可能希望将其保留为字符串数据,因为 ByteArray 经过优化,可以存储和操作文本,而不是用于数值计算。
Cairo 通过设计将布尔值 (true/false) 与数字类型分开,但允许使用 .into() 将布尔值转换为 felt252,其中 true 变为 1,false 变为 0。但是,不能使用自动强制转换方法将布尔值直接转换为整数类型,如 u32、u64 等:
fn main() {
let flag: bool = true;
// This works, bool to felt252:
// 这有效,bool 到 felt252:
let as_felt: felt252 = flag.into(); // Works: true becomes 1, false becomes 0
// 有效:true 变为 1,false 变为 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
};
}
数字不能自动强制转换回布尔值。需要显式比较,如下所示:
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;
}
布尔值也不能直接用作数组索引或算术运算中:
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];
}
因此,关键点是:bool → felt252 自动工作,但 bool → 整数类型需要手动转换,并且没有自动强制转换在相反方向上有效。
在大多数情况下,布尔值应保持为布尔值,以获得更好的类型安全性和代码清晰度。
Cairo 中的类型转换强调安全性和显式性,而不是自动转换。它使用 Into 进行有保证的转换,并使用 TryInto 进行可能失败的转换,从而强制人们显式处理潜在的错误。
这种方法可以防止其他语言中常见的静默数据丢失,并在错误到达生产环境之前捕获它们。Cairo 的显式转换意味着你的智能合约会以可预测的方式运行,即使在处理意外数据时也是如此。
与 Solidity 不同,Solidity 需要手动安全检查类型转换,Cairo 通过 Option 类型将错误处理直接构建到其强制转换系统中。
虽然 Cairo 的转换要求比隐式强制转换需要更多的代码,但这种显式性可以构建更可靠的智能合约。
本文是 Starknet 上的 Cairo 编程 系列教程的一部分
- 原文链接: rareskills.io/post/cairo...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!