如果你用过其他编程语言(C/C++、Python、Java、Ruby、Lisp 等),很可能见过 null 或 nil。
这些语言用 null 或 nil 来表示某个变量没有值。
但这也很容易(而且经常)让人不小心用了 null 变量。
可以想象,试图调用一个不存在的函数,或者访问一个不存在的值,会引发各种各样的 bug 和崩溃。
null 的创造者甚至把它称为自己的“十亿美元错误”。
Option 类型为了避免这些问题,Cairo 的变量可以用 Option 枚举安全地表示没有值的情况。
这个枚举有两个变体:None,它在 Cairo 中相当于 null;以及 Some(T),其中 T 是任意类型的值。
它长这样:
enum Option<T> {
None,
Some(T),
}
你可以把 Option 看作你和空引用可能引发的问题之间的一层保护,同时又保留了空引用在概念上的用处。
Option
把变量设为 None 相当直接:
let nothing: Option<u32> = Option::None; // Variable nothing is set to None
不过,如果你想让 Option 类型携带一个值,就不能直接赋这个值。
Option 类型的变量和比如说 i32 类型的变量并不等同。
你需要用 Some:
let wrong_way: Option<i32> = -4; // This will not work
let right_way: Option<i32> = Option::Some(-4); // This will work
let another_right_way = Option::Some(-4); // Compiler infers that this is Option<i32>
同样的道理,下面这样也不行:
let number = 47;
let option_number = Option::Some(15);
let compile_error = number + option_number; // Cannot add an i32 and an Option<i32> - they are of different types
要取出 Some 中包含的值,你需要先检查它是否存在:
let mut some_words = Option::Some("choose something to say"); // some_words set to something
match some_words {
Option::Some(str) => println!("Here, we will {}", str),
Option::None => println!("I've got nothing to say"),
} // Prints, "Here, we will choose something to say"
some_words = Option::None; // some_words now set to None
// Exactly the same match block as above
match some_words {
Option::Some(str) => println!("Here, we will {}", str),
Option::None => println!("I've got nothing to say"),
} // Prints, "I've got nothing to say"
除了 match,Cairo 还有其他工具可以用来检查和访问 Option 中保存的值,不过现在你应该已经熟悉 match 了。
可以把这看作一个示例,说明 Cairo 为什么用 Option 而不是空引用。
关键在于,你_必须_检查 Option 变量是 Some(这种情况下你可以放心提取并使用其中包含的值),还是 None。
否则你的程序无法编译;编译器会保护你免受 null 的困扰。
你正在实现一个角色扮演游戏。 玩家的角色由以下结构表示:
pub struct Player {
health: u32,
mana: Option<u32>,
level: u32,
}
游戏中的玩家必须达到 10 级才能解锁法力池,解锁后即可用法力池施放法术。
在此之前,玩家的法力为 Option::None。
你要实现这个游戏中的两个功能:复活机制和施法机制。
revive方法应检查玩家是否确实已经死亡(生命值降到了 0);如果确实死亡,该方法应返回一个生命值为 100 的新玩家实例。
revive方法应保留玩家的等级。
如果玩家的等级达到或超过 10 级,复活后应拥有 100 点法力。
如果玩家的等级低于 10 级,其法力应为 Option::None。
let dead_player = Player { health: 0, mana: Option::None, level: 2 };
dead_player.revive()
// Returns Player { health: 100, mana: Option::None, level: 2 }
如果对生命值为 1 或更高的玩家调用revive方法,该方法应返回Option::None。
let alive_player = Player { health: 1, mana: Option::Some(15), level: 11 };
alive_player.revive()
// Returns Option::None
cast_spell方法接受一个指向玩家的可变引用,以及一个mana_cost参数,表示施放该法术需要消耗多少法力。
它返回所施放法术造成的伤害值。
如果玩家还无法使用法力池,尝试施放法术时必须让玩家的生命值减少该法术的法力消耗。 返回的伤害必须为 0。
let not_a_wizard_yet = Player { health: 79, mana: Option::None, level: 9 };
assert_eq!(not_a_wizard_yet.cast_spell(5), 0)
assert_eq!(not_a_wizard_yet.health, 74);
assert_eq!(not_a_wizard_yet.mana, Option::None);
如果玩家有法力池但法力不足,该方法不应影响法力池。 返回的伤害必须为 0。
let low_mana_wizard = Player { health: 93, mana: Option::Some(3), level: 12 };
assert_eq!(low_mana_wizard.cast_spell(10), 0);
assert_eq!(low_mana_wizard.health, 93);
assert_eq!(low_mana_wizard.mana, Option::Some(3));
否则,法术施放成功。
应从玩家的法力池中扣除mana_cost。
返回的伤害必须是该法术法力消耗的两倍。
let wizard = Player { health: 123, mana: Option::Some(30), level: 18 };
assert_eq!(wizard.cast_spell(10), 20);
assert_eq!(wizard.health, 123);
assert_eq!(wizard.mana, Option::Some(20));
玩得开心!