Here's a short program that doesn't work: Rust Playground
I want (a) to be able to dynamically call either write_hello
or write_bye
, (b) but I don't want to want to tie either of these functions to stdout
.
Can anyone suggest a way of achieving this?
Unfortunately, what you're asking for are generic function pointers, which as far as I know don't exist in nearly any statically typed language. But, generic's sibling, dynamics (As in dynamic dispatch), can rescue us here:
use std::io::{Result, Write, stdout};
fn main() {
invoke(write_hello);
}
fn invoke(f: fn(&mut dyn Write) -> Result<()>) -> Result<()> {
let mut stdout = stdout();
f(&mut stdout)
}
fn write_hello(w: &mut dyn Write) -> Result<()> {
writeln!(w, "Hello!")
}
fn write_bye(w: &mut dyn Write) -> Result<()> {
writeln!(w, "Bye!")
}
Playground
This is because dynamic trait objects erase types and instead simply assure the user that the data under their pointer implements said trait.
4 Likes
I don't know what you mean exactly, because your invoke
function currently requires that W = Stdout
when it calls the given function pointer:
use ::std::io::{
self, // Result, /* do not shadow prelude items */
stdout,
Stdout,
Write,
};
fn main ()
{
invoke(write_hello).expect("IO error");
}
fn invoke (
f: fn(&mut Stdout) -> io::Result<()>,
) -> io::Result<()>
{
let mut stdout = stdout();
f(&mut stdout)
}
fn write_hello<W : Write> (w: &mut W) -> io::Result<()>
{
writeln!(w, "Hello!")
}
fn write_bye<W : Write> (w: &mut W) -> io::Result<()>
{
writeln!(w, "Bye!")
}
And as a side note, using a (generic) closure gives users more freedom:
fn invoke (
f: impl FnOnce(&mut Stdout) -> io::Result<()>,
) -> io::Result<()>
{
let mut stdout = stdout();
f(&mut stdout)
}
At call site, you can remain generic, with the following:
fn main ()
{
fn condition () -> bool { ::std::env::args().len() == 42 }
if condition() {
generic_feed(
write_hello,
stdout(),
)
} else {
generic_feed(
write_bye,
stderr(),
)
}.expect("IO error");
}
fn generic_feed<W : Write> (
f: fn(&mut W) -> io::Result<()>,
mut stream: W,
) -> io::Result<()>
{
f(&mut stream)
}
and with a closure:
fn generic_feed<W, F> (
f: F,
mut stream: W,
) -> io::Result<()>
where
W : Write,
F : FnOnce(&mut W) -> io::Result<()>,
{
f(&mut stream)
}
However, if invoke
's function needs to feed either a Stdout
or a Stderr
to the callback / function pointer, then you will need dynamic dispatch,
either (the most flexible way) with a vtable like @OptimisticPeach has shown:
#![deny(bare_trait_objects)]
use ::std::io::{
self,
stderr,
stdout,
Write,
};
fn invoke (
f: fn(&mut (dyn Write + 'static)) -> io::Result<()>,
) -> io::Result<()>
{
fn condition () -> bool { ::std::env::args().len() == 42 }
if condition() {
f(
&mut stdout()
// add vtable
as &mut dyn Write // unified type
)
} else {
f(
&mut stderr()
// add vtable
// as &mut dyn Write // can be implicitly coerced
)
}
}
fn main ()
{
invoke(
write_hello,
).expect("IO error");
}
fn write_hello<W : Write + ?Sized> (w: &mut W) -> io::Result<()>
{
writeln!(w, "Hello!")
}
fn write_bye<W : Write + ?Sized> (w: &mut W) -> io::Result<()>
{
writeln!(w, "Bye!")
}
or, if you know all the input types (e.g. , Stdout
and Stderr
), you can unify the types under an enum
:
use ::std::io::{
self, //Result, /* do not shadow prelude items */
stderr, Stderr,
stdout, Stdout,
Write,
};
/// Unified type (instead of `&mut dyn Write`)
enum StdoutOrStderr<'a> {
Stdout(&'a mut Stdout),
Stderr(&'a mut Stderr),
}
/// Macro that will expand our generic function
/// into the two wanted monomorphisations
macro_rules! write_to_StdoutOrStderr {(
$generic_f:path
) => ({
use $crate::StdoutOrStderr::*;
(|stdout_or_stderr| match stdout_or_stderr {
| Stdout(stdout) => $generic_f(stdout), // one monomoprhisation
| Stderr(stderr) => $generic_f(stderr), // another one
}) // as fn(StdoutOrStderr) -> io::Result<()>
})}
fn invoke (
f: fn(StdoutOrStderr) -> io::Result<()>,
) -> io::Result<()>
{
fn condition () -> bool { ::std::env::args().len() == 42 }
if condition() {
f(
StdoutOrStderr::Stdout(&mut stdout()) // type unification
)
} else {
f(
StdoutOrStderr::Stderr(&mut stderr()) // type unification
)
}
}
fn main ()
{
invoke(
write_to_StdoutOrStderr!(write_hello),
).expect("IO error");
}
fn write_hello<W : Write> (w: &mut W) -> io::Result<()>
{
writeln!(w, "Hello!")
}
fn write_bye<W : Write> (w: &mut W) -> io::Result<()>
{
writeln!(w, "Bye!")
}
As you can see, you can very easily unify under the &mut dyn Write
trait object type, or with an explicit enum
1 Like
system
Closed
August 2, 2019, 10:43pm
4
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.