Cleaner alternative to many unwrap()'s - rust

The following code removes the _ character from png files in a folder:
use std::fs;
use std::path::Path;
fn main() {
let dir = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_file() && path.extension().unwrap() == "png" {
let new_path = path.with_file_name(path.file_name().unwrap().to_str().unwrap().replace("_",""));
fs::rename(path, new_path).unwrap();
}
}
}
As you can see unwrap() is used a lot. It is possible to remove them in this code, and use a cleaner approach?

You're using unwrap for several different things here. Let's break them down.
fs::read_dir(dir).unwrap()
read_dir can fail if an IO error occurs. That's not something under your control, and it's not something you can deal with. Using the excellent vexing exceptions analogy, this error would be an exogenous one: not your fault and not something you can prevent. unwrap makes sense here. In a larger program, we might let our function return io::Result<_> and could write fs::read_dir(dir)? to let the caller try to recover from the error. But for a small main-only program, unwrap makes sense here.
let entry = entry.unwrap();
Same thing. It's an IO error out of your hands. In a larger program, you would write entry? to propagate the error to the caller, but here on this small scale, unwrap is fine.
path.extension().unwrap()
Here's where things get interesting. extension doesn't fail. It returns None in the completely normal, reasonable situation where the file doesn't have an extension. For instance, if the file is named Rakefile or .gitignore. Panicking in this case is really unfortunate. Instead, we simply want the if statement to fail. What your if statement says right now is "assert that the extension exists, and do something if it's png". What you really want is to say "if the extension exists and is png". No assertion necessary. Consider
if let Some(extension) = path.extension() {
if extension == "png" {
...
}
}
In future versions of Rust, it will be possible to write if let in conjunction with &&, so we'll be able to shorten this to
if let Some(extension) = path.extension() && extension == "png" {
...
}
But that feature is unstable right now.
Moving on, I'm skipping over the line with several unwrap calls right now. We'll come back to that in a minute.
fs::rename(path, new_path).unwrap();
fs::rename is an IO operation and can fail like any IO operation can. Let it fail, or propagate in case of a containing function, just like the first two.
Now let's talk about the last line.
path.with_file_name(path.file_name().unwrap().to_str().unwrap().replace("_",""));
file_name() returns None if there's no filename. In that case, we shouldn't even be trying to rename the file, so that should be something we check in an if let before we get here.
if let Some(filename) = path.file_name() {
...
}
Next, you're using to_str. The reason you need to do this is that filenames use OsStr, which may or may not be valid UTF-8. So if you want to panic on such filenames, that's fine. Personally (given how rare and bizarre that situation would be), I'd probably panic as well (or propagate, similar to the other IO exceptions). If you want to recover, you could use to_string_lossy, which replaces invalid UTF-8 sequences with U+FFFD.
If you want to propagate, you can convert Option into io::Result with ok_or_else.
Finally, since you do have a lot of IO going on here, I would actually recommend going ahead and factoring this out into a separate function that results an io::Result. Then main can call unwrap (or expect) once on the result to indicate any IO errors, but other callers could theoretically handle or recover from those same errors.
With all of that in mind, we get down to one expect call in main that deals (uniformly) with all of the IO errors as follows.
use std::fs;
use std::io;
use std::path::Path;
fn replace_files(dir: &Path) -> io::Result<()> {
for entry in fs::read_dir(dir)? {
let path = entry?.path();
if let Some(extension) = path.extension() {
if let Some(filename) = path.file_name() {
if path.is_file() && extension == "png" {
let filename_utf8 =
filename.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Non-UTF-8 filename"))?;
let new_path = path.with_file_name(filename_utf8.replace("_",""));
fs::rename(path, new_path)?;
}
}
}
}
Ok(())
}
fn main() {
let dir = Path::new("/home/alex/Desktop");
replace_files(dir).expect("I/O error occurred!");
}

Related

Error calling a Lua function from Rust: `*mut rlua::ffi::lua_State` cannot be shared between threads safely

I am developing a CLI program for rendering template files using the new MiniJinja library by mitsuhiko.
The program is here: https://github.com/benwilber/temple.
I would like to be able to extend the program by allowing the user to load custom Lua scripts for things like custom filters, functions, and tests. However, I am running into Rust lifetime errors that I've not been able to solve.
Basically, I would like to be able to register a Lua function as a custom filter function. But it's showing an error when compiling. Here is the code:
https://github.com/benwilber/temple/compare/0.3.1..lua
Error:
https://gist.github.com/c649a0b240cf299d3dbbe018c24cbcdc
How can I call a Lua function from the MiniJinja add_filter function? I would prefer to try to do this in the regular/safe way. But I'm open to unsafe alternatives if required.
Thanks!
Edit: Posted the same on Reddit and users.rust-lang.org
Lua uses state that is not safe to use from more than one thread.
A consequence of this is that LuaFunction is neither Sync or Send.
This is being enforced by this part of the error message:
help: within `LuaFunction<'_>`, the trait `Sync` is not implemented for `*mut rlua::ffi::lua_State`
In contrast a minijinja::Filter must implement Send + Sync + 'static.
(See https://docs.rs/minijinja/0.5.0/minijinja/filters/trait.Filter.html)
This means we can't share LuaFunctions (or even LuaContext) between calls to the Filters.
One option is to not pass your lua state into the closures, and instead create a new lua state every call, something like this.
env.add_filter(
"concat2",
|_env: &Environment, s1: String, s2: String|
-> anyhow::Result<String, minijinja::Error> {
lua.context(|lua_ctx| {
lua_ctx.load(include_str!("temple.lua")).exec().unwrap();
let globals = lua_ctx.globals();
let temple: rlua::Table = globals.get("temple").unwrap();
let filters: rlua::Table = temple.get("_filters").unwrap();
let concat2: rlua::Function = filters.get("concat2").unwrap();
let res: String = concat2.call::<_, String>((s1, s2)).unwrap();
Ok(res)
}
}
);
This is likely to have relatively high overhead.
Another option is to create your rlua state in one thread and communicate with it via pipes. This would look more like this:
pub fn test() {
let mut env = minijinja::Environment::new();
let (to_lua_tx, to_lua_rx) = channel::<(String,String,SyncSender<String>)>();
thread::spawn(move|| {
let lua = rlua::Lua::new();
lua.context(move |lua_ctx| {
lua_ctx.load("some_code").exec().unwrap();
let globals = lua_ctx.globals();
let temple: rlua::Table = globals.get("temple").unwrap();
let filters: rlua::Table = temple.get("_filters").unwrap();
let concat2: rlua::Function = filters.get("concat2").unwrap();
while let Ok((s1,s2, channel)) = to_lua_rx.recv() {
let res: String = concat2.call::<_, String>((s1, s2)).unwrap();
channel.send(res).unwrap()
}
})
});
let to_lua_tx = Mutex::new(to_lua_tx);
env.add_filter(
"concat2",
move |_env: &minijinja::Environment,
s1: String,
s2: String|
-> anyhow::Result<String, minijinja::Error> {
let (tx,rx) = sync_channel::<String>(0);
to_lua_tx.lock().unwrap().send((s1,s2,tx)).unwrap();
let res = rx.recv().unwrap();
Ok(res)
}
);
}
It would even be possible to start multiple lua states this way, but would require a bit more plumbing.
DISCLAIMER: This code is all untested - however, it builds with a stubbed version of minijinja and rlua in the playground. You probably want better error handling and might need some additional code to handle cleanly shutting down all the threads.

Are these unwraps necessary?

I'm new to Rust and I see a lot of code looks like this:
use std::thread;
fn main() {
println!("So we start the program here!");
let t1 = thread::spawn(move || {
thread::sleep(std::time::Duration::from_millis(200));
println!("We create tasks which gets run when they're finished!");
});
let t2 = thread::spawn(move || {
thread::sleep(std::time::Duration::from_millis(100));
println!("We can even chain callbacks...");
let t3 = thread::spawn(move || {
thread::sleep(std::time::Duration::from_millis(50));
println!("...like this!");
});
t3.join().unwrap();
});
println!("While our tasks are executing we can do other stuff here.");
t1.join().unwrap();
t2.join().unwrap();
}
As you can see, they invoke join() followed by unwrap(), but they don't use the unwrapped stuff. I tried deleting these unwrap() and it still works. Are these unwrap() necessary? I also noticed even the Rust book use this syntax.
The unwrap() is a shorthand to access the value of a Result or an Option. It's fine when you write some example code as you don't want to deal with the error handling. And if you remove the unwrap() here everything is going to work.
It would be different if for some reason an error will be returned.
In this case, your program will behave differently, with the unwrap(), or without.
In the case you have the unwrap(), the error condition will exit the program.
If you don't use unwrap(), the program will go ahead ignoring the error.
As written before, this is fine for small piece of code, but in a real world scenario, the unwrap() should not be there, instead you should handle the error case.

Some errors E0425 & E0599 write_fmt

mod loginfo{
use std::io::Error;
use chrono::prelude::*;
use std::io::prelude::*;
use std::fs::OpenOptions;
const LOG_SYS :&'static str = "log.txt";
const LOG_ERR :&'static str = "log_error.txt";
pub fn set_log_error(info: String)->Result<(), String>{
let mut handler = OpenOptions::new().append(true)
.open(LOG_ERR);
if handler.is_err(){
create_file(LOG_ERR.to_owned()).unwrap();
set_log_error(info).unwrap();
}
if let Err(_errno) = handler.write_fmt(
format_args!("{:?}\t{:?} ->[Last OS error({:?})]\n",
Utc::now().to_rfc2822().to_string(), info,
Error::last_os_error()) ){
panic!(
"\nCannot write info log error\t Info\t:{:?}\n",
Error::last_os_error());
}
Ok(())
}
pub fn set_log(info: String)->Result<(), String>{
let mut handler = OpenOptions::new().append(true)
.open(LOG_SYS);
if handler.is_err(){
set_log_error("Cannot write info log".to_owned())
.unwrap();
}
if let Err(_errno) = handler.write_fmt(
format_args!("{:?}\t{:?}\n",
Utc::now().to_rfc2822().to_string(), info)){
set_log_error("Cannot write data log file".to_owned())
.unwrap();
}
Ok(())
}
pub fn create_file(filename : String)->Result<(), String>{
let handler = OpenOptions::new().write(true)
.create(true).open(filename);
if handler.is_err(){
panic!(
"\nCannot create log file\t Info\t:{:?}\n",
Error::last_os_error());
}
Ok(())
}
}
When compiling, I get the following errors, "error[E0599]: no method named write_fmt found for enum std::result::Result<std::fs::File, std::io::Error> in the current scope --> src/loginfo.rs:19:38`"
but despite using the right imports, I still get the same errors. Is this due to a bad implementation of the module?
Thank you in advance for your answers and remarks?
+1 #Masklinn Ok I think I understand it would be easier to just write
pub fn foo_write_log( info: String){
let mut handler = OpenOptions::new().append(true)
.create(true).open(LOG_SYS).expect("Cannot create log");
handler.write_fmt(
format_args!("{:?}\t{:?} ->[Last OS error({:?})]\n",
Utc::now().to_rfc2822().to_string(), info,
Error::last_os_error())).unwrap();
}
but despite using the right imports, I still get the same errors. Is this due to a bad implementation of the module?
Kind-of? If you look at the type specified in the error, handler is a Result<File, Error>. And while io::Write is implemented on File, it's not implemented on Result.
The problem is that while you're checking whether handler.is_err() you never get the file out of it, nor do you ever return in the error case. Normally you'd use something like match or if let or one of the higher-order methods (e.g. Result::map, Result::and_then) in order to handle or propagate the various cases.
And to be honest the entire thing is rather odd and awkward e.g. your functions can fail but they panic instead (you never actually return an Err); if you're going to try and create a file when opening it for writing fails, why not just do that directly[0]; you are manually calling write_fmt and format_args why not just write!; write_fmt already returns an io::Error why do you discard it then ask for it again via Error::last_os_error; etc...
It's also a bit strange to hand-roll your own logger thing when the rust ecosystem already has a bunch of them though you do you; and the naming is also somewhat awkward e.g. I'd expect something called set_X to actually set the X, so to me set_log would be a way to set the file being logged to.
[0] .create(true).append(true) should open the file in append mode if it exists and create it otherwise; not to mention your version has a concurrency issue: if the open-for-append fails you create the file in write mode, but someone else could have created the file -- with content -- between the two calls, in which case you're going to partially overwrite the file

How can I work around not being able to export functions with lifetimes when using wasm-bindgen?

I'm trying to write a simple game that runs in the browser, and I'm having a hard time modeling a game loop given the combination of restrictions imposed by the browser, rust, and wasm-bindgen.
A typical game loop in the browser follows this general pattern:
function mainLoop() {
update();
draw();
requestAnimationFrame(mainLoop);
}
If I were to model this exact pattern in rust/wasm-bindgen, it would look like this:
let main_loop = Closure::wrap(Box::new(move || {
update();
draw();
window.request_animation_frame(main_loop.as_ref().unchecked_ref()); // Not legal
}) as Box<FnMut()>);
Unlike javascript, I'm unable to reference main_loop from within itself, so this doesn't work.
An alternative approach that someone suggested is to follow the pattern illustrated in the game of life example. At a high-level, it involves exporting a type that contains the game state and includes public tick() and render() functions that can be called from within a javascript game loop. This doesn't work for me because my gamestate requires lifetime parameters, since it effectively just wraps a specs World and Dispatcher struct, the latter of which has lifetime parameters. Ultimately, this means that I can't export it using #[wasm_bindgen].
I'm having a hard time finding ways to work around these restrictions, and am looking for suggestions.
The easiest way to model this is likely to leave invocations of requestAnimationFrame to JS and instead just implement the update/draw logic in Rust.
In Rust, however, what you can also do is to exploit the fact that a closure which doesn't actually capture any variables is zero-size, meaning that Closure<T> of that closure won't allocate memory and you can safely forget it. For example something like this should work:
#[wasm_bindgen]
pub fn main_loop() {
update();
draw();
let window = ...;
let closure = Closure::wrap(Box::new(|| main_loop()) as Box<Fn()>);
window.request_animation_frame(closure.as_ref().unchecked_ref());
closure.forget(); // not actually leaking memory
}
If your state has lifetimes inside of it, that is unfortunately incompatible with returning back to JS because when you return all the way back to the JS event loop then all WebAssembly stack frames have been popped, meaning that any lifetime is invalidated. This means that your game state persisted across iterations of the main_loop will need to be 'static
I'm a Rust novice, but here's how I addressed the same issue.
You can eliminate the problematic window.request_animation_frame recursion and implement an FPS cap at the same time by invoking window.request_animation_frame from a window.set_interval callback which checks a Rc<RefCell<bool>> or something to see if there's an animation frame request still pending. I'm not sure if the inactive tab behavior will be any different in practice.
I put the bool into my application state since I'm using an Rc<RefCell<...>> to that anyway for other event handling. I haven't checked that this below compiles as is, but here's the relevant parts of how I'm doing this:
pub struct MyGame {
...
should_request_render: bool, // Don't request another render until the previous runs, init to false since we'll fire the first one immediately.
}
...
let window = web_sys::window().expect("should have a window in this context");
let application_reference = Rc::new(RefCell::new(MyGame::new()));
let request_animation_frame = { // request_animation_frame is not forgotten! Its ownership is moved into the timer callback.
let application_reference = application_reference.clone();
let request_animation_frame_callback = Closure::wrap(Box::new(move || {
let mut application = application_reference.borrow_mut();
application.should_request_render = true;
application.handle_animation_frame(); // handle_animation_frame being your main loop.
}) as Box<FnMut()>);
let window = window.clone();
move || {
window
.request_animation_frame(
request_animation_frame_callback.as_ref().unchecked_ref(),
)
.unwrap();
}
};
request_animation_frame(); // fire the first request immediately
let timer_closure = Closure::wrap(
Box::new(move || { // move both request_animation_frame and application_reference here.
let mut application = application_reference.borrow_mut();
if application.should_request_render {
application.should_request_render = false;
request_animation_frame();
}
}) as Box<FnMut()>
);
window.set_interval_with_callback_and_timeout_and_arguments_0(
timer_closure.as_ref().unchecked_ref(),
25, // minimum ms per frame
)?;
timer_closure.forget(); // this leaks it, you could store it somewhere or whatever, depends if it's guaranteed to live as long as the page
You can store the result of set_interval and the timer_closure in Options in your game state so that your game can clean itself up if needed for some reason (maybe? I haven't tried this, and it would seem to cause a free of self?). The circular reference won't erase itself unless broken (you're then storing Rcs to the application inside the application effectively). It should also enable you to change the max fps while running, by stopping the interval and creating another using the same closure.

Is it ok to return in main?

Since mains return type is an empty tuple (), is it considered a work-around to use return; in fn main() ? I'd like to end my program but don't want to panic! , I just want to calmly end. Is there a standard way to end main early? Or is this ok to do? I come from a C++ background where if you need to return early from a function which does not return any value, you probably shouldn't be using a void to begin with, so I'm wondering if this is the same case with no return type for main()?
fn main() {
// ...
// if no cline args display usage and end
if matches.free.is_empty() {
print_usage(&program, options);
return;
// program continues
}
It's perfectly fine at the language level to return early like this. In this case, you might also like the std::process::exit function, which also allows setting the return code of the process.

Resources