You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让子结构体持有父结构体某一字段的引用?

解决Rust中Child结构体引用Parent字段的借用检查问题

你遇到的问题本质是Rust借用规则的核心约束:当一个值被可变或不可变借用时,你不能移动它的所有权。原代码里,child先借用了s,随后parent尝试获取s的所有权,这直接触发了借用检查器的错误——因为此时s的借用还处于活跃状态。

下面是几种可行的解决方案,按推荐程度排序:

1. 使用Rc共享所有权(推荐,安全)

如果你的场景是单线程的,最安全的方式是用std::rc::Rc来包裹String,让Parent和Child共享对这个字符串的所有权,这样就不需要依赖生命周期借用了。

修改结构体和代码如下:

use std::rc::Rc;

pub struct Parent {
    s: Rc<String>,
    child: Child,
}

pub struct Child {
    s: Rc<String>,
}

fn main() {
    let s = Rc::new(String::from("hello"));
    // 克隆Rc只是增加引用计数,不会复制底层字符串
    let child = Child { s: Rc::clone(&s) };
    let parent = Parent { s, child };
    
    // 验证两者指向同一个字符串
    assert_eq!(parent.s, parent.child.s);
}

Rc的clone()操作非常轻量,只是在堆上的引用计数加1,底层的字符串只会被创建一次,当最后一个Rc被销毁时,字符串才会被释放。

2. 使用Arc支持多线程

如果你的代码需要在多线程环境下运行,把Rc换成std::sync::Arc即可(Arc是原子引用计数,线程安全),用法和Rc几乎一致:

use std::sync::Arc;

pub struct Parent {
    s: Arc<String>,
    child: Child,
}

pub struct Child {
    s: Arc<String>,
}

// 多线程场景示例
use std::thread;

fn main() {
    let s = Arc::new(String::from("thread-safe"));
    let child = Child { s: Arc::clone(&s) };
    let parent = Parent { s, child };
    
    let parent_clone = Parent {
        s: Arc::clone(&parent.s),
        child: Child { s: Arc::clone(&parent.s) },
    };
    
    thread::spawn(move || {
        println!("{}", parent_clone.s);
    }).join().unwrap();
}

3. 不安全的unsafe代码(不推荐,需谨慎)

如果你确定自己能保证内存安全,可以用unsafe绕过借用检查器,但这会失去Rust的内存安全保障,需要自己确保Parent的生命周期始终长于Child,且字符串不会被提前释放。

示例代码:

pub struct Parent {
    s: String,
    child: Child,
}

// Child用裸指针代替引用
pub struct Child {
    s: *const String,
}

fn main() {
    let s = String::from("unsafe example");
    // 先创建Parent,再给Child的指针赋值
    let mut parent = Parent {
        s,
        child: Child { s: std::ptr::null() },
    };
    // 获取Parent中s的指针
    parent.child.s = &parent.s as *const String;
    
    // 使用时需要转成引用,注意unsafe块
    unsafe {
        println!("{}", *parent.child.s);
    }
}

⚠️ 注意:这种方式非常危险,如果后续代码中Parent被移动或者s被修改/销毁,Child的指针会变成悬垂指针,导致未定义行为。除非万不得已,否则不要使用这种方法。

为什么原代码不行?

再回头看你的原代码:

let s = String::new();
let child = Child { s: &s };
let parent = Parent { child, s };

当你创建child时,&s是对栈上s的不可变借用,此时s的借用生命周期会延续到child存在的整个期间。随后你尝试把s移动到parent里,这相当于把s从栈的一个位置移到另一个位置,原来的借用地址就失效了——Rust的借用检查器正是为了防止这种悬垂引用的出现,所以直接报错。

内容的提问来源于stack exchange,提问作者Mendy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 19:27:26