- 前端
- UI组件
- 3D渲染
- 跨平台
- 游戏开发
【免费下载链接】makepad
Makepad is a creative software development platform for Rust that compiles to wasm/webGL, osx/metal, windows/dx11 linux/opengl
本文以 makepad 仓库中 vendored 的eithercrate(位于 libs/rapier/vendor/either)及其 README.rst 为主体,系统讲解 Rust 通用二分支和类型(sum type)Either<L, R>的设计理念、全部核心 API、三个实用宏、trait 集成能力以及 serde 序列化扩展。读完本文,你将掌握Either在方法链、短路逻辑、异构迭代、IO 抽象与 JSON 反序列化等场景下的完整用法,并能够从源码层面理解其"对称、无偏好"的实现哲学。
一、Either 是什么:一个对称的双分支和类型
Either是 Rust 生态中一个经典且通用的双分支和类型(sum type)。它只有两个变体,定义于 src/lib.rs:
pub enum Either<L, R> { Left(L), Right(R), }其核心设计原则是对称性:Either对Left和Right两个变体一视同仁,没有任何偏好。这一点与标准库的Result形成鲜明对比——Result明确将Err视为错误、将Ok视为成功,是为"成功/失败"语义量身定做的。因此官方文档(README 与 crate 文档均明确说明)建议:如果需要表达成功或错误,请使用标准库的Result;Either是通用类型,适用于"两个候选类型、无优劣之分"的场景。
从源码可以看到,Either派生了一组常用 trait(src/lib.rs):
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum Either<L, R> { ... }同时 crate 顶层还导出了两个便捷别名(src/lib.rs):
pub use crate::Either::{Left, Right};这使得使用者可以像Left(42)、Right("hello")这样直接构造值,而不必每次都写Either::Left(...)。
Either 与 Option、Result 的关系
README 明确指出:"Either has methods that are similar to Option and Result."(Either拥有许多与Option、Result相似的方法)。事实也正是如此——下文的 API 清单中,你会看到left()/right()(对应Option的取值)、map_left()/map_right()(对应map)、left_or_else()/right_or_else()(对应unwrap_or_else)、unwrap_left()/expect_left()(对应unwrap/expect)等熟悉的操作。
此外,Either与Result之间还提供了双向零成本转换(src/lib.rs):
// Result -> Either:Ok => Right,Err => Left impl<L, R> From<Result<R, L>> for Either<L, R> { ... } // Either -> Result:Right => Ok,Left => Err impl<L, R> From<Either<L, R>> for Result<R, L> { ... }从 v1.14.0 开始,Into<Result> for Either被替换为更符合标准惯例的From<Either> for Result(见 README 变更记录),因此let r: Result<i32, String> = either.into();这类写法如今是标准用法。
二、快速开始:如何在 Cargo 中引入 either
README 给出了最简明的依赖配置方式:
[dependencies] either = "1"当前 makepad 仓库中 vendored 的版本是1.15.0,其 Cargo.toml 确认了如下关键信息:
- MSRV(最低支持 Rust 版本):
rust-version = "1.63.0",即需要 Rust 1.63 或更高版本(该要求自 v1.14.0 起生效)。 - 默认特性:
default = ["std"]。 - 可选特性:
std:默认开启,关闭后可让 crate 以#![no_std]模式编译(源码 src/lib.rs 中有#![no_std]与条件extern crate std佐证);serde:默认关闭,开启后为Either派生Serialize/Deserialize;use_std:自 v1.15.0 起被弃用,仅作为std的别名保留(use_std = ["std"]),用于兼容旧配置。
- 许可证:
MIT OR Apache-2.0双许可。
一个典型的 no_std 配置示例:
[dependencies] either = { version = "1", default-features = false }如需 serde 支持:
[dependencies] either = { version = "1", features = ["serde"] }在 makepad 仓库中,either以 vendored 源码形式存在于物理引擎 Rapier 的依赖目录 libs/rapier/vendor/either 下,同一目录内还包含src/(lib.rs、iterator.rs、into_either.rs、serde_untagged.rs、serde_untagged_optional.rs 五个模块文件)、Cargo.toml、LICENSE-APACHE、LICENSE-MIT以及两份 README,属于工程上常见的"锁定第三方依赖版本"做法。
三、核心方法全览:围绕 Left/Right 的 30+ 个 API
Either的主要 API 都定义在 src/lib.rs 的固有实现块中。下面按功能分组梳理。
3.1 变体判断与取值
| 方法 | 说明 |
|---|---|
is_left()/is_right() | 判断当前是否为Left/Right变体 |
left()/right() | 将对应侧的值转换为Option,另一侧得到None(消费 self) |
as_ref() | 将&Either<L, R>投影为Either<&L, &R> |
as_mut() | 将&mut Either<L, R>投影为Either<&mut L, &mut R> |
as_pin_ref()/as_pin_mut() | 对Pin包装的Either进行内部变体的 Pin 投影,供异步与自引用场景使用 |
flip() | 交换左右变体:Left(l) -> Right(l),Right(r) -> Left(r) |
3.2 映射与分支执行
map_left(f):对Left中的值应用f并重新包装为Left,Right原样返回。map_right(f):对称操作,只映射Right。map_either(f, g):两侧分别用不同的函数映射,相当于函数式编程中的bimap(源码注释明确引用 HaskellData.Bifunctor的 bimap 语义)。map_either_with(ctx, f, g):map_either的变体,两个闭包共享一份上下文ctx(适合闭包需要捕获同一可变状态、又要求FnOnce的场景)。either(f, g):两侧各取一个返回值类型相同的函数,把Either"折叠"成单个值。either_with(ctx, f, g):带上下文的either。left_and_then(f)/right_and_then(f):链式求值,类似Option::and_then,闭包返回新的Either。
一个 bimap 的官方示例(源码 doctest):
use either::*; let f = |s: String| s.len(); let g = |u: u8| u.to_string(); let left: Either<String, u8> = Left("loopy".into()); assert_eq!(left.map_either(f, g), Left(5)); let right: Either<String, u8> = Right(42); assert_eq!(right.map_either(f, g), Right("42".into()));3.3 回退值(对应 Option/Result 的 unwrap_or 家族)
left_or(other)/right_or(other):另一侧为空时返回给定值。注意参数是立即求值的,若参数来自函数调用,官方建议改用下面的_or_else版本。left_or_default()/right_or_default():需要L: Default/R: Default。left_or_else(f)/right_or_else(f):惰性求值,闭包接收另一侧的值并计算出回退值。
3.4 解包与断言
unwrap_left()/unwrap_right():直接取出值,若变体不符则panic!(要求另一侧实现Debug,以便 panic 信息打印)。expect_left(msg)/expect_right(msg):带自定义 panic 消息的解包。either_into::<T>():要求L: Into<T>且R: Into<T>,把任一侧统一转换成目标类型T(例如u16/u32统一转u64)。
3.5 迭代相关
into_iter():把Either<L, R>(其中L、R均为IntoIterator且Item 类型相同)转换为Either<L::IntoIter, R::IntoIter>,此后可整体当作一个迭代器使用。iter()/iter_mut():借用迭代版本,同样要求两侧 Item 类型一致。factor_into_iter()/factor_iter()/factor_iter_mut():不要求两侧 Item 类型相同,返回IterEither(见本文第四节),产出"逐元素带 Left/Right 标记"的迭代器。
3.6 factor_* 系列:同质化提取
这一组方法用于把嵌套结构中的"共同类型"提取到外层:
factor_none():Either<Option<L>, Option<R>> -> Option<Either<L, R>>,把None提取出去。factor_err():Either<Result<L, E>, Result<R, E>> -> Result<Either<L, R>, E>,提取同质化的Err类型。factor_ok():Either<Result<T, L>, Result<T, R>> -> Result<T, Either<L, R>>,提取同质化的Ok类型。factor_first():Either<(T, L), (T, R)> -> (T, Either<L, R>),提取二元组的第一个元素。factor_second():Either<(L, T), (R, T)> -> (Either<L, R>, T),提取二元组的第二个元素。
3.7 特殊实现块
- 对
Either<T, T>(两侧同类型):into_inner()直接取出T;map(f)对值应用函数并保持原变体包装。 - 对
Either<&L, &R>与Either<&mut L, &mut R>:cloned()(要求Clone)与copied()(要求Copy)可把引用侧解引用转为拥有值。
四、三个宏:for_both!、try_left! 与 try_right!
README 特别提到try_left!()与try_right!()两个宏,用于短路逻辑;源码中实际还公开了第三个宏for_both!(v1.7.0 起导出)。它们全部定义于 src/lib.rs。
4.1 for_both!:同一条代码处理两个变体
macro_rules! for_both { ($value:expr, $pattern:pat => $result:expr) => { match $value { $crate::Either::Left($pattern) => $result, $crate::Either::Right($pattern) => $result, } }; }语法为either::for_both!(表达式, 模式 => 结果表达式)。当两侧类型不同、却需要对值执行相同操作时(例如统一求len()),它可以避免重复编写两个match分支。官方示例:
use either::Either; fn length(owned_or_borrowed: Either<String, &'static str>) -> usize { either::for_both!(owned_or_borrowed, s => s.len()) } assert_eq!(length(Either::Right("Hello world!")), 12); assert_eq!(length(Either::Left("Hello world!".to_owned())), 12);这个宏是整个 crate 的实现基石——Iterator、Read、Write、Deref、Display等几乎所有 trait 实现都通过它把两个变体的行为统一转发,for_both!之后的闭包得以保持FnOnce/FnMut语义。
4.2 try_left! / try_right!:类似?运算符的短路
macro_rules! try_left { ($expr:expr) => { match $expr { $crate::Left(val) => val, $crate::Right(err) => return $crate::Right(::core::convert::From::from(err)), } }; }try_left!(expr):若expr是Left(val)则取出val继续执行;若是Right(err)则提前返回Right(err)。因此只能用在返回类型为Either的函数内。try_right!(expr):完全对称,提前返回Left。
官方示例:
use either::{Either, Left, Right}; fn twice(wrapper: Either<u32, &str>) -> Either<u32, &str> { let value = either::try_left!(wrapper); Left(value * 2) } assert_eq!(twice(Left(2)), Left(4)); assert_eq!(twice(Right("ups")), Right("ups")); // 短路直接返回注意try_left!提前返回时会对err执行From::from转换,因此允许两侧类型在返回路径上做适当的自动转换。这与Result的?运算符设计思路一致,区别是它作用于Either而非Result。源码中自带测试macros()(src/lib.rs)验证了这两个宏的短路行为。
此外,crate 内部还使用了一个未导出的辅助宏map_either!,用于把两侧变体分别映射为新的Left/Right,大量固有方法(as_ref、map_left等)都由它驱动。
五、trait 集成:Iterator、Read、Write、Future 与更多
README 的核心卖点之一就是Either为众多标准库 trait 提供了转发实现——只要L和R都实现了某 trait,Either<L, R>也就自动实现了它。
5.1 迭代器家族(iterator.rs)
Iterator:要求L: Iterator、R: Iterator<Item = L::Item>(两侧 Item 必须一致)。Either<L, R>的整体行为就是当前活跃侧迭代器的行为,并转发了size_hint、fold、for_each、count、last、nth、collect、partition、all、any、find、find_map、position等一批方法。DoubleEndedIterator:转发next_back、nth_back、rfold、rfind。ExactSizeIterator、FusedIterator:两侧均满足时自动成立。Extend<A>:要求L、R均实现Extend<A>。
// 一个典型的 Either 迭代器用法(源码测试 iter()) let x = 3; let mut iter = match x { 3 => Left(0..10), _ => Right(17..), }; assert_eq!(iter.next(), Some(0)); assert_eq!(iter.count(), 9);这里的关键点是:Either让你能在运行时从两种不同类型的迭代器中选其一,并以统一类型继续链式操作,而无需使用Box<dyn Iterator>造成动态分发。
5.2 IterEither:异构迭代器
IterEither<L, R>(定义于 iterator.rs)由factor_into_iter()/factor_iter()/factor_iter_mut()创建。与Either自身作为迭代器不同,IterEither的Item是Either<L::Item, R::Item>,因此两侧迭代器可以产出不同类型的元素:
let left: Either<_, Vec<u8>> = Left(&["hello"]); assert_eq!(left.factor_into_iter().next(), Some(Left(&"hello"))); let right: Either<&[&str], _> = Right(vec![0, 1]); assert_eq!(right.factor_into_iter().collect::<Vec<_>>(), vec![Right(0), Right(1)]);IterEither同样实现了Iterator、DoubleEndedIterator、ExactSizeIterator与FusedIterator。
5.3 IO 与格式输出(要求 std 特性)
在启用std特性的前提下(src/lib.rs):
Read:转发read、read_exact、read_to_end、read_to_string。这意味着可以把一个"或为文件、或为内存缓冲"的读取源统一抽象为Either<File, &[u8]>。BufRead:转发fill_buf、consume、read_until、read_line。Seek:转发seek(v1.7.0 起加入)。源码测试seek()演示了Left(Cursor::new([]))与Right(Cursor::new(&data[..]))两种 cursor 的统一读写与重定位。Write:转发write、write_all、write_fmt、flush。测试read_write()中Either<Stdin, &[u8]>、Either<Stdout, &mut [u8]>均可直接写入。Error:当L、R都实现Error时,Either<L, R>自身即错误类型,转发source()、description()、cause()。fmt::Display与fmt::Write(v1.14.0 为fmt::Write新增实现):支持直接把Either用于format!或作为write!的目标。
5.4 异步、引用与解引用
Future:当L、R都是Future且Output类型一致时,Either<L, R>本身就是一个Future(v1.8.0 起实现)。这在异步代码里非常有用——分支选择两种不同异步操作而无需装箱。AsRef<T>/AsMut<T>:泛型实现 + 对常见非定长类型的专项实现:str、[T]、Path、OsStr、CStr(后三者需要std,自 v1.5.1 加入)。Deref/DerefMut:当L: Deref、R: Deref<Target = L::Target>时,Either<L, R>可直接解引用到同一目标类型。源码测试deref()展示了Either<String, &str>直接当作&str传入函数。
六、serde 序列化:默认外部标签与 untagged 模式
Either在序列化上有两种姿势,README 与源码对此有明确说明:
- 默认模式:
Either使用 serde 的**外部标签(externally-tagged)**表示,即序列化结果为{"Left": ...}或{"Right": ...}这类带变体名的 JSON。这需要启用serde特性。 - untagged 模式:当某个字段"一般是一个复杂结构,但在典型情况下也可以是更简单的类型"时(例如字段多为
HashMap<String, i32>,但有时只需Vec<String>),可以使用either::serde_untagged模块,不输出任何标签,直接按值序列化。其实现(serde_untagged.rs)内部定义了一个#[serde(untagged)]的镜像枚举,并把公开的serialize/deserialize函数桥接到它上面。
use either::Either; use std::collections::HashMap; #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(transparent)] struct IntOrString { #[serde(with = "either::serde_untagged")] inner: Either<Vec<String>, HashMap<String, i32>> }; // 序列化时不输出任何标签 let data = IntOrString { inner: Either::Left(vec!["Hello".to_string()]) }; assert_eq!(serde_json::to_string(&data)?, r#"["Hello"]"#); // 反序列化:JSON 对象被识别为 Right 分支 let data: IntOrString = serde_json::from_str(r#"{"a": 0, "b": 14}"#)?;either::serde_untagged_optional(serde_untagged_optional.rs)是前者的Option版本,用于Option<Either<L, R>>字段:Some时按值无标签序列化,None时序列化为null。这两种 untagged 模块均自 v1.6.0 引入。
七、IntoEither trait:方法链中便捷地"变成 Either"
v1.11.0 起新增的IntoEithertrait(into_either.rs)为所有Sized类型提供了两个默认方法:
pub trait IntoEither: Sized { fn into_either(self, into_left: bool) -> Either<Self, Self> { ... } fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> where F: FnOnce(&Self) -> bool, { ... } } impl<T> IntoEither for T {}x.into_either(true)得到Left(x),x.into_either(false)得到Right(x);x.into_either_with(predicate)根据谓词函数的结果决定放入Left还是Right。
它让"在方法链的某个节点根据条件切换分支类型"变得非常自然:
use either::{IntoEither, Left, Right}; let x = 0; assert_eq!(x.into_either(true), Left(x)); fn is_even(x: &u8) -> bool { x % 2 == 0 } assert_eq!(x.into_either_with(is_even), Left(x)); assert_eq!(x.into_either_with(|x| !is_even(x)), Right(x));八、版本演进脉络:从 0.1.0 到 1.15.0
README 的 Recent Changes 章节完整记录了 crate 的能力演进史,这里整理为按时间顺序的能力里程碑:
| 版本 | 关键变化 |
|---|---|
| 0.1.0 | 初始发布,支持Iterator、Read、Write |
| 0.1.1 | 实现Deref、DerefMut |
| 0.1.2 | 新增try_left!与try_right!宏 |
| 0.1.3 | 实现Display、Error |
| 0.1.7 | 新增map_left()、map_right()、either(),补充文档 |
| 1.0.0 | 新增默认特性use_std,可关闭 std 链接 |
| 1.0.1 | 修复Iterator的fold转发 |
| 1.0.2 | 转发更多Iterator方法;两侧都实现Extend时实现Extend |
| 1.0.3 | 增加 crate 分类(categories) |
| 1.1.0 | 新增left_and_then、right_and_then;许可证文件纳入仓库 |
| 1.2.0 | 新增either_with() |
| 1.3.0 | 可选 serde 支持 |
| 1.4.0 | 新增固有方法into_iter() |
| 1.5.0 | 新增factor_first()、factor_second()、into_inner() |
| 1.5.1 | 为str、[T]、CStr、OsStr、Path实现AsRef/AsMut |
| 1.5.2 | 新增left_or()、left_or_default()、left_or_else()及右侧对称版本 |
| 1.5.3 | 新增Either<T, T>的map() |
| 1.6.0 | 新增serde_untagged、serde_untagged_optional模块 |
| 1.6.1 | 新增expect_left()、unwrap_left()及右侧对称版本 |
| 1.7.0 | 导出for_both!宏;实现io::Seek;新增either_into()、factor_ok()、factor_err()、factor_none();实现FusedIterator、Error::source特化、Clone::clone_from |
| 1.8.0 | MSRV 提升至 1.36;新增as_pin_ref()、as_pin_mut();实现Future;特化更多io方法 |
| 1.8.1 | 明确多许可证以 OR 组合 |
| 1.9.0 | 新增map_either()、map_either_with() |
| 1.10.0 | 新增factor_iter()、factor_iter_mut()、factor_into_iter()、iter()、iter_mut() |
| 1.11.0 | 新增IntoEithertrait |
| 1.12.0 | MSRV 提升至 1.37;特化nth_back |
| 1.13.0 | 新增cloned()、copied() |
| 1.14.0 | MSRV 提升至 1.63;实现fmt::Write;以From<Either> for Result替代Into<Result> |
| 1.15.0 | 修复无 std 时的 serde 支持;std成为默认特性名,use_std退化为别名 |
当前仓库 vendored 的正是最新稳定线1.15.0(见 Cargo.toml 中version = "1.15.0"与rust-version = "1.63.0")。
九、许可证说明
either采用与 Rust 项目兼容的双重许可:Apache License 2.0 或 MIT License,使用者可任选其一。两条许可证文本均随 crate 分发,位于 libs/rapier/vendor/either/LICENSE-APACHE 与 libs/rapier/vendor/either/LICENSE-MIT。自 v1.1.0 起,许可证文件被同时包含在仓库与发布产物中,便于下游合规审计;v1.8.1 起 README 明确说明两份许可以 OR 逻辑组合。
十、总结与选型建议
- 当你需要在两个不同类型间做运行时选择、且两侧地位平等时,
Either<L, R>是最轻量的方案:它没有 trait object 的动态分发开销,也没有额外堆分配,Copy/Ord/Hash等派生能力使其可以嵌入到几乎所有数据结构中。 - 当两侧需要统一迭代、统一读写时,
Either的 trait 转发实现(Iterator、Read、Write、Future等)让你用"值组合"代替"类型擦除"。 - 当一侧代表失败、另一侧代表成功时,请遵循官方建议改用
Result;需要短期将Result接入Either世界时,可以使用双向From转换无缝切换。 - 在 makepad 仓库中,本 crate 以 vendored 形式随物理引擎 Rapier 的依赖树一起维护,完整源码与测试(src/lib.rs 内嵌的
basic、macros、deref、iter、seek、read_write、error等测试)均可直接在 libs/rapier/vendor/either 目录下查阅验证。
- 前端
- UI组件
- 3D渲染
- 跨平台
- 游戏开发
【免费下载链接】makepad
Makepad is a creative software development platform for Rust that compiles to wasm/webGL, osx/metal, windows/dx11 linux/opengl
相关推荐
fp-ts Json 模块完全指南:基于 Either 的安全 JSON 解析与序列化
fp ts Json 模块完全指南:基于 Either 的安全 JSON 解析与序列化 导读 fp ts/Json 模块(自 v2.10.0 起提供)为 Typ
开发工具language-ext 中 Either 单子完全指南:Left/Right 构造、短路绑定与 BiMap 双路映射
language ext 中 Either 单子完全指南:Left/Right 构造、短路绑定与 BiMap 双路映射 本篇指南以 language ext 仓
后端Rust Package 与 Crate 完全指南:基于 book 仓库源码解析 `cargo new` 的产物结构与 crate 根编译约定
Rust Package 与 Crate 完全指南:基于 book 仓库源码解析 cargo new 的产物结构与 crate 根编译约定 导读 本文以 Rus
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考