< 返回版块

eric642 发表于 2024-04-11 17:50

use axum::{
    http::{self, header::HeaderMap, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
    routing::{get, post},
    Json, Router,
};

tokio::task_local! {
    static NUMBER: u32;
    static HM: HeaderMap;
}


async fn headers(hs: HeaderMap) {
    let hs = hs.clone();
    HM.scope(hs, async move {
        // 这里会有个报错说Option<&HeaderValue>的生命周期要大于入参c的生命周期,这种情况我怎么标注生命周期解决呢
        let host = HM.with(|c: &HeaderMap| -> Option<&HeaderValue> { c.get("Host") });
        println!("{:?}", HM);
    })
    .await;
}

评论区

写评论
TinusgragLin 2024-04-12 15:52

了解了,clone 应该不会有很大问题,刚才我看到 HeaderValue 的内部 是一个 Bytes

pub struct HeaderValue {
    inner: Bytes,
    is_sensitive: bool,
}

如果我没记错的话,bytes::Bytes 就是为了零拷贝解码设计的,自身依赖于引用计数,clone 应该会很便宜,它的 文档也有说 “A cheaply cloneable and sliceable chunk of contiguous memory.”。

--
👇
eric642: 这里我想的场景是:客户端调用-->a服务---->b服务。有一些请求头可能是需要从客户端一直透传,那么用locakkey存这些请求头或者其他业务无关的信息,而后在发送http请求的地方透传出去。http请求放到with里感觉不合适,也没法异步

--
👇
TinusgragLin: 说起来,为啥不直接把处理代码放到 LocalKey::with 的闭包当中?这应该就是这个 API 如此设计的初衷。

作者 eric642 2024-04-12 14:47

这里我想的场景是:客户端调用-->a服务---->b服务。有一些请求头可能是需要从客户端一直透传,那么用locakkey存这些请求头或者其他业务无关的信息,而后在发送http请求的地方透传出去。http请求放到with里感觉不合适,也没法异步

--
👇
TinusgragLin: 说起来,为啥不直接把处理代码放到 LocalKey::with 的闭包当中?这应该就是这个 API 如此设计的初衷。

TinusgragLin 2024-04-12 11:10

说起来,为啥不直接把处理代码放到 LocalKey::with 的闭包当中?这应该就是这个 API 如此设计的初衷。

yuyidegit 2024-04-12 10:59

找了一下,发现这个是基于thread local的,thread local本身有无法拿到引用的限制 https://github.com/rust-lang/rfcs/pull/461#issuecomment-63542891

TinusgragLin 2024-04-12 10:33

如果可以自己进行安全性保证的话,应该可以“做一个违背祖宗的决定”(雄凤山音):

HM.scope(hs, async move {
    // SAFETY:
    // 此处的安全性由以下几点保证:
    // 1. 返回的引用没有以**任何形式**被发送到其他 task。
    // 2. 在第一点的保证下,只有这个 task 可以访问这个 task local 的 HeaderMap;
    //    而在这个 task 中,除了此次访问外,没有其他访问,因此返回的引用是一直有效的,
    //    没有因为数据被修改而使引用失效的风险。
    let host = HM.with(|c: &HeaderMap| -> Option<& /* 'static */ HeaderValue> {
        unsafe { std::mem::transmute(c.get("Host")) }
    });
    println!("{:?}", HM);
})
.await;
作者 eric642 2024-04-12 09:31

看起来都不可避免的要进行内存拷贝

vSylva 2024-04-12 01:09

cloned可

async fn headers(hs: HeaderMap) {
    let hs = hs.clone();
    HM.scope(hs, async move {
        let host = HM.with(|c: &HeaderMap| -> Option<HeaderValue> {
            let h = c.get("Host");
            h.cloned()
        });
        println!("{:?}", HM);
    })
    .await;
}
TinusgragLin 2024-04-12 00:46

我刚刚发现标准库就有一个 Option::cloned 可以用!

let host = HM.with(|c| c.get("Host").cloned());

--
👇
ggggjlgl: 谢谢,看起来干净多了,刚才怎么找都找不到隐去这个None的方式。

TinusgragLin: @ggggjlgl 的回答可以稍微简化一下:

let host = HM.with(|c| c.get("Host").map(Clone::clone));
ggggjlgl 2024-04-12 00:38

谢谢,看起来干净多了,刚才怎么找都找不到隐去这个None的方式。

TinusgragLin: @ggggjlgl 的回答可以稍微简化一下:

let host = HM.with(|c| c.get("Host").map(Clone::clone));
TinusgragLin 2024-04-12 00:31

@ggggjlgl 的回答可以稍微简化一下:

let host = HM.with(|c| c.get("Host").map(Clone::clone));
TinusgragLin 2024-04-12 00:19

总之,我猜这个 LocalKey::with API 这么设计应该是有理由的。

TinusgragLin 2024-04-12 00:17

从安全角度上考虑,如果你可以这么做,那么 LocalKey::with(f) 就有可能会返回一个对 task local 数据的引用,那你是不是可以把这个引用发到其他 task 上,那这个数据就不怎么 task local 了。

TinusgragLin 2024-04-12 00:11

我的猜测是:

我们的这个闭包,类型是 for<'a> FnOnce(&'a A) -> Option<&'a B>,注意这个闭包函数的返回值类型是一个关于生命周期变量 'a 的函数。

再来看 LocalKey::with<R, F: FnOnce(&T) -> R>(..., f: F),这里 F 的类型是 for<'a> FnOnce(&'a T) -> R,注意这里的类型 R 一旦被确定,就是一个常量,并不是关于生命周期变量 'a 的函数。

fn ohno<T>(f: impl Fn(&i32) -> T) {}
fn ok(f: impl Fn(&i32) -> &i32) {}

fn okok<'a, T>(f: impl Fn(&'a i32) -> T) {}

fn test() {
    ohno(|x| x); // 会出现一样的错误
    ok(|x| x); // 编译通过
    okok(|x| x); // 编译通过
}
ggggjlgl 2024-04-11 23:08
// 我才炼气期二层,我也晕了,要不别省内存了,直接to_owned。。。
let host = HM.with(|c| c.get("Host").map_or(None, |x| Some(x.to_owned())));

nemolc: 大佬能不能就简单的修复楼主的问题让代码跑起来。。。

nemolc 2024-04-11 22:37

大佬能不能就简单的修复楼主的问题让代码跑起来。。。

--
👇
ggggjlgl: ```rust // 我觉得这种应该没必要添加生命周期注释,让编译器自动推断就行吧。。。 use std::collections::HashMap;

fn main() { f() }

fn f() { let host:fn(&HashMap<i32, i32>) -> Option<&i32> = |c| c.get(&1); let mut x = HashMap::new(); x.insert(1, 6); println!("{:?}", host(&x).unwrap()); }



作者 eric642 2024-04-11 21:49

这个写法似乎不能解决问题,编译器还是报错

--
👇
全称量词是儿子: let host = HM.with(|c: &'a HeaderMap| -> Option<&'b HeaderValue> where 'a:'b { c.get("Host") });

ankoGo 2024-04-11 21:16

你不要听信其他人说生命周期不重要,在rust中如果没学会生命周期的标注的话,基本寸步难行,如果你是之前写过go的人,那几乎生命周期的标注会无处不在,因为go的编程思维都是引用,然后循环嵌套结构体,只要最里层的结构体有一个引用的字段,则嵌套它的所有父结构体都需要标注这个生命周期,如果函数中还用到这个引用字段,那几乎每一层结构体的函数或者方法都需要标注这个生命周期,当然这些如果都熟悉,其实还是很容易的

ankoGo 2024-04-11 21:12

let host = HM.with(|c: &'a HeaderMap| -> Option<&'b HeaderValue> where 'a:'b { c.get("Host") });

1 共 18 条评论, 1 页