A complete Rust noob with about a week's experience with the language writes...
Say I have the following code:
use std::io::Write;
struct Foo {
msg : String,
}
impl Foo {
fn print(&self) {
write!(std::io::stdout(), "{}", self.msg).unwrap();
}
}
fn a_large_chain_of_function_calls_and_object_lookups(a : Foo, b: Foo) {
a.print();
b.print();
}
fn main() {
let a = Foo {msg: "Hello ".to_string()};
let b = Foo {msg: "world!\n".to_string()};
a_large_chain_of_function_calls_and_object_lookups(a,b);
}
How do I change Foo and main (but not a_large_chain_of_function_calls_and_object_lookups) so that the output can go to a file (or for testing purposes some in-memory buffer) instead of stdout?
In C++ for instance, I could just add a ostream to Foo, which can be pointed to a file, std::cout or a stringstream. But I'm struggling to see how to easily do this in Rust.
I can't give a &mut Write to Foo, because I can't give a mutable reference to stdout to two different objects. I can't give it a function pointer because std::io::stdout's signature is
a) beyond my abilities to easily implement
b) fundamentally tied to fs:std::StdOut, so even if I could implement a fake, it couldn't point to a random file or -in-memory buffer
I appear to be missing something fairly obvious here, can someone point it out?
thanks
Mark
1 Like
This is really hard to answer, because it's not really clear why you're trying to do this. Rust cares about ownership and aliasing, so the answer to questions like this often depends a lot on the specific details of what you're trying to do. If you're looking for "how do I replicate this general pattern from (some other language) for all use cases", you'll often be bitterly disappointed.
I mean, you can use Stdout
without needing mutable access at all, so you could trivially store an Stdout
in each Foo
. If you want to store Write
s, you can use Box<Write>
, but then you'd have to change the middle function, although I'm not sure why you can't change it. You could get away with Rc<WriteWrapper>
or some other custom trait if you're only using IO types that don't actually require mutable access (which I believe files are), but you haven't been that specific.
So my hards are tied enough that all I can really suggest is the bog-standard cop-out approach of last resort: shove it in an Rc<RefCell<_>>
.
use std::cell::RefCell;
use std::io::{self, Write};
use std::rc::Rc;
struct Foo {
msg: String,
out: Rc<RefCell<Write>>,
}
impl Foo {
fn print(&self) {
let mut out = self.out.borrow_mut();
write!(out, "{}", self.msg).unwrap();
}
}
fn a_large_chain_of_function_calls_and_object_lookups(a : Foo, b: Foo) {
a.print();
b.print();
}
fn main() {
let out = Rc::new(RefCell::new(io::stdout()));
let a = Foo {
msg: "Hello ".to_string(),
out: out.clone(),
};
let b = Foo {
msg: "world!\n".to_string(),
out: out,
};
a_large_chain_of_function_calls_and_object_lookups(a,b);
}
1 Like
Rc<RefCell> should do the trick, thanks. I'd missed borrow_mut.
To answer your other points
I can store Stdout instances in Foo, but that only allows me to write to stdout. I'd like Foo to be able to write to anything: stderr, files, tcp connections, and in-memory buffers for tests.
As for not wanting to alter a_large_chain_of_function_calls_and_object_lookups, I could just add a mutable Write to every function call between main and Foo. But given that none of the intermediate functions care about the output, that would be inelegant to say the least (wouldn't work if I ever wanted multiple threads).
But thinking about it, while the other languages I know have straightforward ways of doing this in single-threaded environments, they do all require external guards to stop two threads tramping over themselves if they want to write at the same time. So I can see why Rust has made my single-threaded job a little harder - because it makes the multi-threaded case a lot safer
and Rc<RefCell> isn't that hard to write.
Right now to wrap my head round std::fmt::Write, std::io::Write, whether I can use both or either with strings and stdout...
thanks
Mark
Actually, Rc<RefCell<_>>
can't be used across threads. It's about protecting you from data races in single-threaded code. The problem that almost everyone forgets is that, even with a single thread, it's possible to mutate something from different places in the call stack (imagine doing read a
, call function (which writes to a
), write a
).
The equivalent type for multithreaded code is Arc<Mutex>, btw
I'm so nearly nearly there...
I've tweaked my code and it builds and works with the Rc<RefCell<Write>>
. Woot!
Now in my test for Foo, I want to create a Cursor, create a Foo that writes to the cursor, and then check what was written.
Now I can cast a Cursor
to a Write
.
I can cast a RefCell<Cursor>
to a RefCell<Write>
But I can't cast a Rc<RefCell<Cursor>>
to a Rc<RefCell<Write>>
So if I create a Rc<RefCell<Cursor>>
in my test, then I can't pass it into Foo.
And if I create a Rc<RefCell<Write>>
in my test, then I can't use the innards as a cursor any more, so I can't access what was written. From what I've read, Rust doesn't support down/dynamic casting...
Do I need to make Foo generic/ Something like
struct Foo<T: Write> {
msg: String,
out: Rc<RefCell<T>>,
}
or is there some way to do the casting I want in my test?
thanks for your time.
Works for me. If you have a problem, you need to show what you're doing and why it doesn't work. Something that can be run in the playpen is best, because problems in Rust can be very context-dependent.
Edit: To be clear, all I did was take the previous example and replace io::stdout()
with io::Cursor::new(vec![0u8; 80])
.
What the heck!? As you say, making that change to the previous example works fine. But my seemingly identical actual program fails, and I narrowed it down to this...
let cursor = Cursor::new(vec![0u8; 80]);
// can cast a Cursor to a &Write
let w: &Write = &cursor;
let cursor_cell = RefCell::new(Cursor::new(vec![0u8; 80]));
// can cast a RefCell<Cursor> to a &RefCellWrite<Write>
let w_cell: &RefCell<Write> = &cursor_cell;
let cursor_cell_rc = Rc::new(RefCell::new(Cursor::new(vec![0u8; 80])));
// but this cast fails to compile
let w_cell_rc: &Rc<RefCell<Write>> = &cursor_cell_rc;
The compiler fails with
|
53 | let w_cell_rc: &Rc<RefCell<Write>> = &cursor_cell_rc;
| ^^^^^^^^^^^^^^^ expected trait std::io::Write, found struct `std::io::Cursor`
|
= note: expected type `&std::rc::Rc<std::cell::RefCell<std::io::Write>>`
= note: found type `&std::rc::Rc<std::cell::RefCell<std::io::Cursor<std::vec::Vec<u8>>>>`
But hey, I've now got working code and non-working code. After the break I can iterate them towards each other until I track down what the problem is.
Thanks for your help, and Merry Christmas!
Problem appears to be because I was casting a reference to Rc. This fails:
let w_cell_rc: &Rc<RefCell<Write>> = &cursor_cell_rc;
But this works fine.
let w_cell_rc: Rc<RefCell<Write>> = cursor_cell_rc;
All working now. Thanks for you help
1 Like