簡體   English   中英

將發送特征添加到裝箱特征對象時的奇怪行為

[英]Strange behavior when adding the Send trait to a boxed trait object

這是一個錯誤結構:

#[derive(Debug)]
pub struct Error {
    msg: &'static str,
  //source: Option<Box<dyn std::error::Error>>,        // old
    source: Option<Box<dyn std::error::Error + Send>>, // new
}

impl Error {
    fn new_caused<E>(msg: &'static str, err: E) -> Self
    where
        E: 'static + std::error::Error + Send,
    {
        Self {
            msg: msg,
            source: Some(Box::from(err)),
        }
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(fmt, "{}", self.msg) // HACK
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.source.as_ref().map(|err| err.as_ref())
    }
}

fn main() {
    let err = "this will fail".parse::<i32>().unwrap_err();
    let err = Error::new_caused("some msg", err);
}

我決定讓它能夠Send所以我將source: Option<Box<dyn std::error::Error>>更改為source: Option<Box<dyn std::error::Error + Send>>並且奇怪的事情發生了。

魔術 #1

new_caused拒絕再編譯:

error[E0277]: the trait bound `std::boxed::Box<dyn std::error::Error + std::marker::Send>: std::convert::From<E>` is not satisfied
  --> src/main.rs:14:26
   |
14 |             source: Some(Box::from(err)),
   |                          ^^^^^^^^^^^^^^ the trait `std::convert::From<E>` is not implemented for `std::boxed::Box<dyn std::error::Error + std::marker::Send>`
   |
   = note: required by `std::convert::From::from`

Box::from更改為Box::new幫助,即使它們的簽名看起來相同並且Box::from的實現只是調用Box::new

魔術 #2

source也變得不正確:

error[E0308]: mismatched types
  --> src/main.rs:27:9
   |
27 |         self.source.as_ref().map(|err| err.as_ref())
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait `std::error::Error`, found trait `std::error::Error + std::marker::Send`
   |
   = note: expected enum `std::option::Option<&(dyn std::error::Error + 'static)>`
              found enum `std::option::Option<&dyn std::error::Error + std::marker::Send>`

為什么未使用的Send特性不像其他特性那樣被忽略?

用手動版本替換該組合器邏輯工作正常:

fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    match &self.source {
        Some(source) => Some(source.as_ref()),
        None => None
    }
}

概括

這種“魔法”的解釋是什么,有什么更好的處理方法?

對於魔法 #1,這是因為標准庫具有以下實現:

impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a>
impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a>

E: Error + Send without the Sync沒有實現。

簡單的解決方案#1:在有Send地方添加Sync或使用Box::new

Magic #2 更復雜:你有一個std::option::Option<&dyn std::error::Error + Sync> ,你需要一個Option<&dyn std::error::Error> 您知道&(dyn std::error::Error + Send)可轉換為&dyn std::error::Error因此您希望Option<_>也是如此,但這些轉換不是可傳遞的1 ,所以它失敗了。

mapmatch的區別在於類型推導的順序:

map情況下,閉包的類型被推導出為Box<dyn std::error::Error + Sync> 由於它返回類型為&dyn std::error::Error + Sync err.as_ref() ,這就是閉包返回的類型。 然后Option::map返回一個Option<_>與閉包返回類型相同的類型,所以你得到一個最終的Option<&dyn std::error::Error + Sync>和一個錯誤。

match代碼中,當你寫Some(source) => Some(source.as_ref())source被推導為類型Box<dyn std::error::Error + Sync> ,但右側是從返回類型Option<&dyn std::error::Error> ,因此Some的參數被source.as_ref()轉換為該類型: source.as_ref()被轉換為正確的類型並進行編譯。

我認為編寫此示例的最簡單方法是在映射中添加一個 cast as _ ,以指示編譯器從用法而不是從內部代碼推斷閉包的類型:

self.source.as_ref().map(|err| err.as_ref() as _)

如果代碼比較復雜, as _可能不可行。 那么一場match就足夠了。

帶有固定代碼的游樂場


1:我想我讀過關於使這些轉換自動(自動特征的協變?),但我在任何地方都找不到它......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM