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

在Map结构体字段中实现Trait泛型的Rust技术问题

基于Rocket的Security Voter权限控制实现方案

问题背景

想要在Rocket项目中实现类似Symfony Security Voter的权限控制体系,设计Security结构体存储不同主题(如"books""authors")对应的权限处理器(实现SecurityVoter trait的结构体),并将Security实例存入Rocket的State中。初始实现遇到编译错误,调整后仍不确定方案合理性,寻求正确实现建议。

初始代码

SecurityVoter trait定义

pub trait SecurityVoter {
    /// Gets the "subject" supported by the voter
    fn supports(&self) -> String;
    /// Takes a given right and a user, and checks if the user has_access to the action represented by the right.
    fn has_access(&self, right: &str, user: User) -> bool;
}

Security结构体定义

#[derive(Default)]
pub struct Security<T>
where
    T: SecurityVoter,
{
    voters: HashMap<String, T>,
}

Security方法实现

impl<T> Security<T>
where
    T: SecurityVoter,
{
    pub fn add_handler(&mut self, handler: T) -> &mut Self {
        self.voters.insert(handler.supports(), handler);

        self
    }

    pub fn has_access(&self, subject: &str, right: &str, user: &User) -> bool {
        if self.voters.contains_key(subject) {
            return self.voters.get(subject).unwrap().has_access(right, user);
        }

        false
    }
}

编译错误信息

error[E0599]: the function or associated item `default` exists for struct `core::security::Security<dyn core::security::SecurityVoter>`, but its trait bounds were not satisfied
  --> src/core/security.rs:68:59
   |
6  | / pub struct Security<T>
7  | | where
8  | |     T: SecurityVoter,
9  | | {
10 | |     voters: HashMap<String, T>,
11 | | }
   | | -
   | | |
   | |_function or associated item `default` not found for this
   |   doesn't satisfy `_: std::default::Default`
...
38 |   pub trait SecurityVoter {
   |   -----------------------
   |   |
   |   doesn't satisfy `_: std::default::Default`
   |   doesn't satisfy `_: std::marker::Sized`
...
68 |           let mut security = Security::<dyn SecurityVoter>::default(); // <- line with the error
   |                                                             ^^^^^^^ function or associated item cannot be called on `core::security::Security<dyn core::security::SecurityVoter>` due to unsatisfied trait bounds
   |
note: the following trait bounds were not satisfied:
      `dyn core::security::SecurityVoter: std::default::Default`
      `dyn core::security::SecurityVoter: std::marker::Sized`
  --> src/core/security.rs:5:10
   |
5  | #[derive(Default)]
   |          ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
   = note: the following trait bounds were not satisfied:
           `dyn core::security::SecurityVoter: std::marker::Sized`
           which is required by `core::security::Security<dyn core::security::SecurityVoter>: std::default::Default`
           `dyn core::security::SecurityVoter: std::default::Default`
           which is required by `core::security::Security<dyn core::security::SecurityVoter>: std::default::Default`

修改后的尝试代码

pub struct Security<T: ?Sized> {
    voters: HashMap<String, Box<T>>,
}

impl<T> Security<T>
where
    T: SecurityVoter + ?Sized,
{
    pub fn new() -> Self {
        Self {
            voters: HashMap::new(),
        }
    }

    pub fn add_handler(&mut self, handler: Box<T>) -> &mut Self {
        self.voters.insert(handler.supports(), handler);

        self
    }

    pub fn is_granted(user: &User, roles: Vec<&str>) -> bool {
        roles
            .iter()
            .all(|item| user.roles.contains(&item.to_string()))
    }

    pub fn has_access(&self, subject: &str, right: &str, user: &User) -> bool {
        if self.voters.contains_key(subject) {
            return self.voters.get(subject).unwrap().has_access(right, user);
        }

        false
    }
}

// ...

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Default)]
    struct TestSecurityHandler {}

    impl SecurityVoter for TestSecurityHandler {
        fn supports(&self) -> String {
            "test".to_string()
        }

        fn has_access(&self, right: &str, _user: &User) -> bool {
            if right == "ACCEPT_ACCESS" {
                return true;
            }

            false
        }
    }

    #[derive(Default)]
    struct STestSecurityHandler {}

    impl SecurityVoter for STestSecurityHandler {
        fn supports(&self) -> String {
            "test".to_string()
        }

        fn has_access(&self, right: &str, _user: &User) -> bool {
            if right == "ACCEPT_ACCESS" {
                return true;
            }

            false
        }
    }

    #[test]
    fn test_custom_security_handler() {
        let mut security = Security::<dyn SecurityVoter>::new(); // <- line with the error

        security.add_handler(Box::new(TestSecurityHandler::default()));
        security.add_handler(Box::new(STestSecurityHandler::default()));

        let user = User::default();

        assert!(security.has_access("test", "ACCEPT_ACCESS", &user));
    }
}

正确实现建议

1. 简化Security结构体设计(移除泛型)

因为需要存储多种不同类型的SecurityVoter实现,直接使用trait对象+Box存储,无需给Security加泛型,避免Sized约束问题:

use std::collections::HashMap;

pub struct Security {
    voters: HashMap<String, Box<dyn SecurityVoter>>,
}

2. 修正SecurityVoter trait的约束与方法签名

  • 为适配Rocket State的要求(Send + Sync + 'static),给trait添加对应的约束
  • 统一has_access的user参数为引用类型,避免不必要的拷贝:
pub trait SecurityVoter: Send + Sync + 'static {
    fn supports(&self) -> String;
    fn has_access(&self, right: &str, user: &User) -> bool;
}

3. 完善Security的方法实现

  • new方法直接创建空的HashMap
  • add_handler自动为实现类装箱,简化调用方代码
  • 优化has_access的错误处理(避免unwrap):
impl Security {
    pub fn new() -> Self {
        Self {
            voters: HashMap::new(),
        }
    }

    // 自动接收任意实现SecurityVoter的类型并装箱
    pub fn add_handler<V: SecurityVoter>(&mut self, handler: V) -> &mut Self {
        let subject = handler.supports();
        self.voters.insert(subject, Box::new(handler));
        self
    }

    pub fn is_granted(user: &User, roles: &[&str]) -> bool {
        roles
            .iter()
            .all(|&role| user.roles.contains(&role.to_string()))
    }

    pub fn has_access(&self, subject: &str, right: &str, user: &User) -> bool {
        self.voters
            .get(subject)
            .map_or(false, |voter| voter.has_access(right, user))
    }
}

4. 调整测试代码

无需手动指定泛型和装箱,直接调用方法:

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Default)]
    struct TestSecurityHandler {}

    impl SecurityVoter for TestSecurityHandler {
        fn supports(&self) -> String {
            "test".to_string()
        }

        fn has_access(&self, right: &str, _user: &User) -> bool {
            right == "ACCEPT_ACCESS"
        }
    }

    #[derive(Default)]
    struct STestSecurityHandler {}

    impl SecurityVoter for STestSecurityHandler {
        fn supports(&self) -> String {
            "test".to_string()
        }

        fn has_access(&self, right: &str, _user: &User) -> bool {
            right == "ACCEPT_ACCESS"
        }
    }

    #[test]
    fn test_custom_security_handler() {
        let mut security = Security::new();

        security.add_handler(TestSecurityHandler::default());
        security.add_handler(STestSecurityHandler::default());

        let user = User::default();

        assert!(security.has_access("test", "ACCEPT_ACCESS", &user));
        assert!(!security.has_access("test", "DENY_ACCESS", &user));
        assert!(!security.has_access("unknown", "ACCEPT_ACCESS", &user));
    }
}

5. 集成到Rocket State

在Rocket启动时初始化Security实例并放入State:

use rocket::Build;

#[launch]
fn rocket() -> _ {
    let mut security = Security::new();
    // 添加业务相关的权限处理器
    security.add_handler(TestSecurityHandler::default());

    rocket::build()
        .manage(security) // 将Security存入State
        .mount("/", routes![...])
}

在请求处理函数中使用State:

use rocket::State;

#[get("/books/<id>")]
fn get_book(id: u32, security: &State<Security>, user: &User) -> Result<String, String> {
    if security.has_access("books", "VIEW", user) {
        Ok(format!("Book {}", id))
    } else {
        Err("Unauthorized".to_string())
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 22:45:40