【问题标题】:How to put a reference to a trait object in an Option?如何在选项中引用特征对象?
【发布时间】:2019-04-20 08:18:15
【问题描述】:

我想在结构中的Option 中存储对io::Write 特征对象的引用,但我不知道如何。我可以像这样直接把引用放进去:

pub struct Parameters<'a> {
    pub log: &'a (io::Write + 'a),
    // Other elements removed
}

然后从(例如)BufWriter 分配它,如下所示:

let logstream = &BufWriter::new(f);
let parameters = Parameters {
    log: logstream, // Other elements removed
};

这可行,但我希望 logstream 是可选的。如果我尝试:

pub struct Parameters<'a> {
    pub log: Option<&'a(io::Write + 'a)>,
    // Other elements removed
}

let logstream = match f {
    Some(f) => Some(&BufWriter::new(f)),
    None => None,
};

let parameters = Parameters {
    log: logstream,
    // Other elements removed
};

我明白了:

error[E0308]: mismatched types
  --> src/main.rs:17:14
   |
17 |         log: logstream,
   |              ^^^^^^^^^ expected trait std::io::Write, found struct `std::io::BufWriter`
   |
   = note: expected type `std::option::Option<&dyn std::io::Write>`
              found type `std::option::Option<&std::io::BufWriter<std::vec::Vec<u8>>>`

这里有什么合适的方法?

【问题讨论】:

  • 一旦你通过类型检查器,借用检查器就会杀死你(BufWriter 活得不够长)。

标签: rust traits trait-objects


【解决方案1】:

您必须以某种方式显式键入带有 trait 类型的 BufWriter,并且您还必须更加小心生命周期。

这是如何工作的草图:

use std::io;
use std::io::BufWriter;
use std::str::from_utf8;

pub struct Parameters<'a> {
    pub log: Option<&'a mut io::Write>,
    // Other elements removed
}

fn main() {
    let mut w = BufWriter::new(Vec::new());
    let rw: &mut std::io::Write = &mut w;
    let logstream = Some(rw);

    let parameters = Parameters {
        log: logstream,
        // Other elements removed
    };
    parameters.log.unwrap().write(b"hello world").ok();

    println!("{}", from_utf8(&w.into_inner().unwrap()).unwrap());
}

【讨论】:

  • 谢谢,星蓝。实际上,我想我可能已经使用 Option> 找到了另一个答案 - 一旦我完成检查,我会在这里发布以供评论。
【解决方案2】:
use std::io;
use std::io::BufWriter;

pub struct P<'a> {
    pub log: Option<&'a mut io::Write>,
}

fn file() -> Option<Vec<u8>> {
    Some(Vec::new())
}

fn main() {
    let mut ow = file().map(|f| BufWriter::new(f));
    let p = P {
        log: ow.as_mut().map(|w| w as &mut io::Write),
    };

    p.log.unwrap().write(b"Hi!").ok();

    println!(
        "{}",
        String::from_utf8(ow.unwrap().into_inner().unwrap()).unwrap()
    );
}

与其采用Option&lt;W&gt; 并尝试将其一次性转换为Option&lt;&amp;mut io::Write&gt;,不如分三步完成:

  • Option&lt;W&gt; -> Option&lt;io::BufWriter&lt;W&gt;&gt; 使 ow。这就是拥有BufWriter 的东西。正如另一个答案中所指出的,如果您尝试在模式匹配中创建一个BufWriter,然后立即对其进行引用,则该引用的生命周期范围在模式匹配的分支内,因此借用检查器会抱怨。
  • 下一步是使用as_mut()Option&lt;io::BufWriter&lt;W&gt;&gt; 转换为Option&lt;&amp;mut io::BufWriter&lt;W&gt;&gt;
  • 最终从对实现的引用转换为通用特征对象。在这种情况下,地图包含演员表。我曾尝试使用into,但似乎没有实现在对特征的实现者的引用和对特征对象的引用之间进行转换。

在本例中,file() 返回 Vec&lt;u8&gt; 作为底层编写器,我这样做是为了解决方案是 testable in a Rust playground,但这也可以是 File

最后,我删除了 trait 对象的生命周期约束,类型系统/借用检查器似乎认为没有必要。

【讨论】:

【解决方案3】:

感谢以上回答。在他们发布之前,我自己有一个想法并想出了这个(简化为一个或多或少显示我正在尝试做的事情的最小示例):

use std::{env, process};
use std::fs::File;
use std::io::{self, BufWriter};

pub struct Parameters {
    pub log: Option<Box<dyn io::Write>>,
}


fn main() {
    let args: Vec<String> = env::args().collect();
    let logfile = if args.len() > 1 {
        Some(&args[1])
    } else {
        None
    };

    let logstream: Option<Box<dyn io::Write>> = match logfile {
        Some(logfile) => {
            let f = File::create(logfile).unwrap_or_else(|err| {
                eprintln!("ERROR opening logfile: {}", err);
                process::exit(1);
            });
            let logstream = BufWriter::new(f);
            Some(Box::new(logstream))
        }
        None => {
            None
        },
    };

    let parameters = Parameters {
        log: logstream,
    };
    play(parameters);
}

fn play(parameters: Parameters) {
    // Rest of code removed
    if let Some(mut stream) = parameters.log {
        stream.write("Testing\n".as_bytes()).unwrap_or_else(|err| {
            eprintln!("ERROR writing to logfile: {}", err);
            process::exit(1);
        });
    }
}

这是一个好的解决方案还是上面建议的其中一个更好?

【讨论】:

  • 这并没有回答最初提出的问题:如何将 reference 放到 Option 中的 trait 对象?跨度>
  • Rust 有一个极其丰富的标准库,其中包含您从头开始编写的许多习语。例如,您可以使用env::args().nth(1) 选择性地获取第一个参数,而不是使用process::exit,我会将main 的返回类型更改为io::Result&lt;()&gt; 并使用? 运算符来传播错误。这是一个改编的Rust playground
  • @Shepmaster - 感谢您的帮助。也许这并不能真正回答所写的问题,尽管据我了解, Box 是对堆上对象的一种引用。也许我的问题还不够清楚,因为这确实实现了我所需要的 - 可以选择在我的库外部创建作者并将其传入。
  • @amnn - 感谢有关 args().nth(1) 的提示 - 我仍在学习 Rust,而且库很大,所以需要一段时间才能意识到这些东西的存在。至于 process::exit() - 是的,那只是为了一个快速而肮脏的演示 - 通常我会返回一个 Result() 对象并使用 ? (我在我的真实代码中非常广泛地使用 ?)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-08
  • 1970-01-01
  • 1970-01-01
  • 2016-02-14
  • 2021-05-11
  • 2022-01-05
相关资源
最近更新 更多