Rust 协作:类型边界比口头约定更可靠
协作时最容易含糊的是函数会不会接管数据。接口若写成 fn send(payload: String),调用方就知道之后不能再用 payload;若只需读取,就该写 &str。
fn render(name: &str) -> String { format!("hello, {name}") }
我会把这个选择写在接口注释和 PR 描述里,并附一段调用代码。不要用“为了性能”笼统解释所有权转移,先说明谁负责释放、谁还要复用。涉及用户文本时,示例统一用 example,不能把协作沟通里的原始内容粘进仓库。
让签名先表达使用方式
需要修改调用方数据时才接收 &mut,需要长期持有时才接收所有权。把参数一律写成 String 看似省事,却让复制和释放的位置变得不清楚。返回值也一样:只是格式化就返回新字符串,需要复用原数据就返回借用。异步边界不能随意保存借用值,必要时在创建任务前复制最小字段。
评审时问一个问题
我会问:“这个值在函数返回后,谁还要用?”答案不清楚,接口通常还没设计好。PR 里附上调用前后的短例子,读代码的人就不必猜所有权。类型报错不是阻碍,它是在指出责任还没有写清楚。
别把所有权简化成性能结论
借用不必然更快,取得所有权也不必然更慢,要看调用边界。只格式化一次内容,接收 &str 让调用方保留原值,语义就够清楚;后台任务要保存内容直到结束,接收 String 反而更诚实。为了强行借用而把生命周期参数铺进多层接口,常常让调用者更难用,也未必省掉必要的分配。
评审时我会让提交者说明数据离开当前函数后的去向。若马上交给 spawn,就在创建任务前构造需要的所有权值;若只是本次渲染读一下,就不要为未来假设提前 clone。这样讨论的是代码里看得见的使用方式,而不是抽象地争论哪种写法高级。
示例要让调用方看到差异
render("example") 只能说明函数会读取文本,不能说明传入的 String 之后是否还能使用。文档可补一小段:创建 name,调用 render(&name),再把 name 交给另一个函数。接管所有权的接口也给出相反例子,读者就不会觉得变量无故消失。若多个任务共享可变状态,先考虑把更新汇到一个拥有者;把 &mut 改成共享容器并不是小改动,它改变了谁能在何时修改数据。
让错误信息变成接口讨论的材料
同事贴出一次所有权报错时,评审不该只回“加 clone”。先看错误里哪个值被移动、谁还要在后面读取,以及复制后是否会出现两份可变数据。若调用者确实需要保留原值,借用或提取小字段更合适;若后续不再需要,接管所有权就比隐式复制清楚。把这几句写进 PR,比给出一句语法答案更能统一团队的选择。
接口稳定后再调整名字和注释。参数名若叫 payload,注释应说明它会被消费还是只读;函数名若暗示发送,读者自然会预期所有权可能转移。类型、命名和示例表达的是同一件事,彼此矛盾时,使用者就只能通过试编译来猜规则。
评审结束前,我会让另一位调用者按示例写一小段使用代码。若他仍分不清该传 String、&str 还是 &mut,说明签名或文档还没有把责任讲清,而不是读者“不熟 Rust”。这种短反馈往往比继续补术语更能发现接口问题。
团队约定可以写进贡献指南:新增公开函数时说明借用期限、是否跨异步边界和失败后的数据归属。它不替代代码评审,却让讨论有同一套起点。随着接口变多,靠记忆维持这种一致性很容易遗漏。
403



被折叠的 条评论
为什么被折叠?



