expected only a single parent argument
主线位置:span 主线开始之前。
#[instrument]的参数在编译期由attr.rs解析,这里报错,span!根本不会生成。
flowchart TB
tgt["target = app"] --> settgt["args.target 有值"]
settgt --> par["parent = None"]
par --> chk{"args.target 有值?"}
chk -->|"是(查错了字段)"| boom["报 single parent"]
swap["parent 写在前面"] --> chk2{"args.target 有值?"}
chk2 -->|否| ok["正常解析"]
现象
只写了一个 parent,编译器却说「只能有一个」:
tracing = "=0.1.44"
tracing 0.1.44 依赖 tracing-attributes 0.1.31,Cargo.lock 里解析到的就是这个版本。
use tracing::instrument;
#[instrument(target = "app", parent = None)]
fn detached() {}
fn main() {}
rustc 1.99.0:
error: expected only a single `parent` argument
--> src/bin/err_target_parent.rs:3:30
|
3 | #[instrument(target = "app", parent = None)]
| ^^^^^^
follows_from 也一样:
use tracing::{Span, instrument};
#[instrument(target = "app", skip(cause), follows_from = [cause])]
fn later(cause: &Span) {}
error: expected only a single `follows_from` argument
--> src/bin/err_target_follows.rs:3:43
|
3 | #[instrument(target = "app", skip(cause), follows_from = [cause])]
| ^^^^^^^^^^^^
条件是 target 出现在 parent / follows_from 前面。只写 parent、只写 target,或者 parent 在前,都能编译。
从哪来
InstrumentArgs 的 Parse 是一个循环,每轮认一个关键字,解析前先检查这一项是不是已经出现过(整个循环的写法见 参数)。第 97 行起:
} else if lookahead.peek(kw::target) {
if args.target.is_some() {
return Err(input.error("expected only a single `target` argument"));
}
let target = input.parse::<StrArg<kw::target>>()?.value;
args.target = Some(target);
} else if lookahead.peek(kw::parent) {
if args.target.is_some() {
return Err(input.error("expected only a single `parent` argument"));
}
let parent = input.parse::<ExprArg<kw::parent>>()?;
args.parent = Some(parent.value);
} else if lookahead.peek(kw::follows_from) {
if args.target.is_some() {
return Err(input.error("expected only a single `follows_from` argument"));
}
let follows_from = input.parse::<ExprArg<kw::follows_from>>()?;
args.follows_from = Some(follows_from.value);
parent 和 follows_from 两个分支检查的都是 args.target,而不是自己的字段。于是:
target已经解析过,再遇到parent/follows_from,就报「only a single」。报错位置input.error(...)指的是当前 token,也就是parent这个关键字,所以箭头落在它上面。parent写两次反而不报错:检查的args.target是None,第二次解析直接覆盖args.parent,后一个生效。
修法
绕过:把 parent / follows_from 写在 target 前面。解析循环本身不在乎顺序,生成的代码一样。
tracing = "=0.1.44"
tracing-subscriber = { version = "=0.3.23", default-features = false, features = ["fmt", "std"] }
use tracing::{Span, info_span, instrument};
use tracing_subscriber::fmt::format::FmtSpan;
// 绕过:parent 写在 target 前面
#[instrument(parent = None, target = "app")]
fn detached() {}
// 对照:不写 parent
#[instrument(target = "app")]
fn attached() {}
// 0.1.31 里重复的 parent 不报错,后一个生效
#[instrument(parent = None, parent = Span::current())]
fn twice() {}
fn main() {
tracing_subscriber::fmt()
.with_ansi(false)
.without_time()
.with_span_events(FmtSpan::NEW)
.init();
info_span!("outer").in_scope(|| {
detached();
attached();
twice();
});
}
M2 Max、rustc 1.99.0、--release,跑 3 次输出相同:
INFO outer: target_parent_ok: new
INFO detached: app: new
INFO outer:attached: app: new
INFO outer:twice: target_parent_ok: new
detached的 target 是app,span 链里没有outer:parent = None和target都生效了。attached在outer里,是对照。twice写了两次parent,没有报错,父 span 是outer:后一个Span::current()覆盖了None。修复发布以后,这种写法会报「expected only a singleparentargument」,这次是该报的。
调换顺序这个写法在修复发布后也照样能编译,不用改回来。
修复:PR #3630(「attributes: fix duplicate check for parent and follows_from」)把两处 args.target 分别改成 args.parent 和 args.follows_from,并加了两个 UI 测试:tests/ui/fail/duplicate_args.rs(重复的 parent / follows_from 要报错)和 tests/ui/pass/target_with_parent.rs(target 在前要能编译)。PR 说明里列了这个 bug 的两个后果:
- A repeated
parentorfollows_fromis accepted, and the last value wins.targetfollowed byparentorfollows_fromis rejected.#[instrument(target = "my_target", parent = None)]fails withexpected only a single `parent` argument.
它在 2026-10-09 合进 main(提交 c78ceb06)。本卷写作时(2026-10),本地仓库里没有任何 tag 包含这个提交,GitHub 上 tracing-attributes 最新的 tag 仍是 tracing-attributes-0.1.31,也就是说还没有包含修复的版本。
parent 参数同龄parent 和 follows_from 是 tracing-attributes 0.1.21 一起加的,CHANGELOG 写的是:「#[instrument(follows_from = ...)] argument for setting one or more follows-from span ([#2093])」和「#[instrument(parent = ...)] argument for overriding the generated span's parent ([#2091])」。PR #2091 的提交 4c0e30fb 加 parent 分支时,重复检查就写成了 if args.target.is_some();随后 PR #2093 的提交 138a9718 照着加 follows_from 分支,带上了同一行。从 0.1.21(2022-04-26)到 0.1.31,这两行一直没变。#3621 报告时只发现了「重复的 parent 不报错」这一半,把它叫作「a copy-paste residue」;target 在前就编译失败这一半,是写 #3630 的测试时才发现的:「The second effect is not in the issue. I found it while writing the tests.」
怎么确认自己没踩
这是编译错误,踩了就会看到。要确认的是两件事:
- 自己的代码里有没有重复写
parent/follows_from。0.1.31 不报,后一个生效;等修复发布、升级 tracing-attributes 后会变成编译错误。 - 当前用的 tracing-attributes 版本:
cargo tree -i tracing-attributes显示0.1.21到0.1.31之间的版本,就有这个 bug。包含修复的版本发布以后,查它的 CHANGELOG 里有没有 #3630。
小结
- tracing-attributes 0.1.31(以及 0.1.21 起的所有版本)里,
parent和follows_from的重复检查查的是args.target。 - 后果一:
target写在它们前面就报「expected only a singleparentargument」(或follows_from)。后果二:它们自己写两次不报错,后一个生效。 - 绕过:把
parent/follows_from写在target前面;修复发布后这个写法照样有效。 - 修复 #3630 已于 2026-10-09 合进
main,截至 2026-10 还没有包含它的版本。