我已经有一段时间喜欢在Rust上编程了,当我试图创建抽象时,有一些东西让我很恼火.下面是一个小例子,我用依赖项注入风格编写程序时得到的代码类型:

type UserId = u8;

pub struct User {}

pub struct UserDatabase {}

impl UserDatabase {
    pub fn get(&self, user_id: UserId) -> User {
        todo!()
    }
}

pub struct UserService {
    user_database: UserDatabase
}

impl UserService {
    fn get_user(&self, user_id: UserId) -> User {
        self.user_database.get(user_id)
    }
}

fn main() {
    let user_database = UserDatabase{};

    let user_service = UserService{
        user_database
    };

    user_service.get_user(todo!());
}

在本例中,UserService公开了get_user方法.在内部,这称为UserDatabase.在main方法中,这UserDatabase被注入到UserService中.

因此,这一切都很好,但考虑现在的情况下,这UserDatabase个需要使用(注入)另一个服务:

pub struct SomeOtherService {
    user_database: UserDatabase
}

fn main() {
    let user_database = UserDatabase{};

    let user_service = UserService{
        user_database
    };

    let some_other_service = SomeOtherService{
        user_database
    };

    user_service.get_user(todo!());
}

显然,这段代码无法编译,因为我们在main中try 了两次移动user_database.

所以我们必须把它包装成Rc号:

let user_database = Rc::new(UserDatabase{});

但这意味着现在,为了能够将其注入user_service,我们还必须将UserService struct 中的类型封装在Rc中:

pub struct UserService {
    user_database: Rc<UserDatabase>
}

这让我很困扰,因为:

  • 改变了UserService的外部使用方式,迫使我们改变了UserService的内部 struct .
  • UserService现在意识到UserDatabase在其他地方使用的事实.UserService只需要UserDatabase就可以调用1个函数.为什么它必须意识到UserDatabase在其他地方被引用的事实?客观地说,我知道答案:这是因为程序必须解决如何在不干扰代码其他部分的情况下安全地从堆中取消对值的引用;但是作为程序员,我们不想把不必要的细节泄露给函数的实现.

类似地,对于Arc这样的对象,程序的所有部分现在都必须知道它们是在多线程场景中运行的.

我们有没有办法实现一种UserService不知道UserDatabase是如何包装的 struct ?我想到的一个 idea 是有一个特点:

trait GetUserDatabase {
    fn get(&self, user_id: UserId) -> User;
}

然后以某种方式让Rc<UserDatabase>Arc<UserDatabase>人实现这种trait .

也许我在这里错过了一些巧妙的技巧,或者也许我只是需要在处理依赖注入之类的模式时改变我传统的Golang/Java思维方式.

推荐答案

Rust并不是为Java的依赖注入心态而设计的.

UserService现在意识到UserDatabase在其他地方使用的事实.UserService只需要UserDatabase就可以调用1个函数.为什么它必须意识到UserDatabase在其他地方被引用的事实?

因为这正是Rust想要达到的目标.

这不仅仅是一个限制:Rust想让你知道谁拥有你的物品.所有权制度应该设计你的思想和计划.这种"共享风格"确实不太生疏——这也是Rc和friends在惯用语言Rust中不常见的原因之一(它们确实出现了,但与GC'd语言的数量不同).

不要考虑服务,要考虑数据.与其问who needs access to the user database,不如问of whom it is.店主没有把DB发给任何需要它的人,他只是让他们看一看.如果main()拥有数据库,服务应该引用它(或者根本不存在).如果这两个服务都拥有它,它应该是Rc.不管怎样,你都必须明确这一点.这是件好事!

Rust相关问答推荐

在没有引用计数或互斥锁的情况下,可以从Rust回调函数内的封闭作用域访问变量吗?

MutexGuard中的过滤载体不需要克隆

如何使用syn插入 comments ?

如何修复数组中NewType导致的运行时开销

Trait bound i8:来自u8的不满意

Rust面向对象设计模式

获取已知数量的输入

是否可以使用Serde/Rust全局处理无效的JSON值?

如何将带有嵌套borrow /NLL 的 Rust 代码提取到函数中

如何执行数组文字的编译时串联?

实现 Deref 的 struct 可以返回对外部数据的引用吗?

Rust:为什么 Pin 必须持有指针?

如何将 C++ 程序链接到 Rust 程序,然后将该 Rust 程序链接回 C++ 程序? (cpp -> rust -> cpp)

(let b = MyBox(5 as *const u8); &b; ) 和 (let b = &MyBox(5 as *const u8); ) 之间有什么区别

Rust/Serde/HTTP:序列化`Option`

无法理解 Rust 对临时值的不可变和可变引用是如何被删除的

使用 rust 在 google cloud run (docker) 中访问环境变量的适当方法

当 T 不是副本时,为什么取消引用 Box 不会抱怨移出共享引用?

如何将切片推入数组?

如何构建包含本地依赖项的 docker 镜像?