< 返回版块

javin158 发表于 2020-08-06 07:04

我想封装一个支持异步的recvmsg接口,使用了smol库和nix库,定义了如下的结构体:

use smol::Async;
use std::os::unix::net::UnixStream ;

pub struct UnixSeqPacket(Async<UnixStream>);

nix库提供的recvmsg接口的原型如下:

pub fn recvmsg<'a>(fd: RawFd, iov: &[IoVec<&mut [u8]>],                                                                                                                                         
                   mut cmsg_buffer: Option<&'a mut Vec<u8>>,                                                                                                                                    
                   flags: MsgFlags) -> Result<RecvMsg<'a>>  

smol::Async提供一个用于异步读取的接口,如下:

pub async fn read_with_mut<R>(
        &mut self,
        op: impl FnMut(&mut T) -> io::Result<R>,
    ) -> io::Result<R>

我想封装的recvmsg打算使用和nix库一致的接口原型,如下最直接的封装会产生编译错误:

pub async fn recvmsg<'a>(&mut self, iov: &[IoVec<&mut [u8]>],                                                                                                                             
          mut cmsg_buffer: Option<&'a mut Vec<u8>>,                                                                                                                                             
          flags: MsgFlags) -> io::Result<RecvMsg<'a>>  
{
   self.0.read_with_mut(|s| 
      recvmsg(s.as_raw_fd(), iov, cmsg_buffer, flags)                                                                                                                               
              .map_err( |err|                                                                                                                                                                   
                  match err {                                                                                                                                                                   
                      nix::Error::Sys(nix::errno::EWOULDBLOCK) =>                                                                                                                               
                          io::Error::from(io::ErrorKind::WouldBlock),                                                                                                                           
                      _ => io::Error::new( io::ErrorKind::Other,                                                                                                                                
                          format!("fail to recvmsg, err is {}", err)),                                                                                                                          
                  }                                                                                                                                                                             
              ) 
   ).await
}

编译错误如下:

error[E0507]: cannot move out of `cmsg_buffer`, a captured variable in an `FnMut` closure
   --> src/unix_seqpacket.rs:115:41
    |
95  |         mut cmsg_buffer: Option<&'a mut Vec<u8>>,
    |         --------------- captured outer variable
...
115 |             recvmsg(s.as_raw_fd(), iov, cmsg_buffer, flags)                                                                                                                               
    |                                         ^^^^^^^^^^^
    |                                         |
    |                                         move occurs because `cmsg_buffer` has type `std::option::Option<&mut std::vec::Vec<u8>>`, which does not implement the `Copy` trait
    |                                         help: consider borrowing the `Option`'s content: `cmsg_buffer.as_ref()`

错误原因是 cmsg_buffer: Option<&'a mut Vec> 包含了可变的引用,所以在closure中使用时不能Copy,只能Move。但是smol::Async的接口read_with_mut需要传入的是FnMut,而closure将变量Move后它的类型就是FnOnce了,不能是FnMut了。

我目前能想到的解决方法是使用unsafe,将cmsg_buffer: Option<&'a mut Vec> 在clousre中复制多份,如下所示:

pub async fn recvmsg<'a>(&mut self, iov: &[IoVec<&mut [u8]>],
        mut cmsg_buffer: Option<&'a mut Vec<u8>>,
        flags: MsgFlags) -> io::Result<RecvMsg<'a>>
    {
      self.0.read_with_mut(|s|  {
            let cmsg_buffer_tmp =  cmsg_buffer.as_mut()
               .map(|buf| { unsafe { std::mem::transmute_copy(buf) } } );
            recvmsg(s.as_raw_fd(), iov, cmsg_buffer_tmp, flags)
            .map_err( |err|
                match err {
                    nix::Error::Sys(nix::errno::EWOULDBLOCK) => 
                        io::Error::from(io::ErrorKind::WouldBlock),
                    _ => io::Error::new( io::ErrorKind::Other,
                        format!("fail to recvmsg, err is {}", err)),
                }
            ) }
           ).await
   }

请各位帮忙看下是否有更好的解决方法,谢谢!

评论区

写评论
作者 javin158 2020-08-07 13:48

cmsg_buffer 不是必读的,所以使用Option。即使不用Option包裹一层,由于可变引用不能copy,只能move,处理起来仍然有类似的问题。

--
👇
zhouzuoji: 原因是闭包这个匿名结构用move的方式捕获了cmsg_buffer, 但是在闭包内的函数调用里把匿名结构的成员以move方式传递给read_with_mut函数,而总所周知move结构的某个成员是不可行的。

其实我很奇怪的是为什么cmsg_buffer的类型会用Option<&mut Vec>? 为什么要包一层Option? 难道要进行读取你还能传个None?

zhouzuoji 2020-08-07 10:48

原因是闭包这个匿名结构用move的方式捕获了cmsg_buffer, 但是在闭包内的函数调用里把匿名结构的成员以move方式传递给read_with_mut函数,而总所周知move结构的某个成员是不可行的。

其实我很奇怪的是为什么cmsg_buffer的类型会用Option<&mut Vec>? 为什么要包一层Option? 难道要进行读取你还能传个None?

1 共 2 条评论, 1 页