title | timestamp | author | published | description | tags | todo | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Getting file extension in Rust using PathBuf |
2024-04-07 02:20:01 -0700 |
szabgab |
true |
How to get the file extension in Rust |
|
|
In some applications I need to extract the extension of a file. The part after the dot. We can do that easily using the extension
method of PathBuf
.
let path = PathBuf::from("hello.txt");
println!("{:?} {:?}", path, path.extension().unwrap());
This will work, however if the path does not have an extension, the extension
method will return None
and the unwrap
will generate a panic:
let path = PathBuf::from("hello");
println!("{:?} {:?}", path, path.extension().unwrap());
Will get you this:
thread 'main' panicked at src/main.rs:8:50:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
One way to handle this problem is to use match
. It has two arms, one to handle the case when extension
returns
Some
value, that's when there was an extension. The other arm is when extension
returns None
. In which case
we just report the lack of extension.
The drawback of this approach is that after the closing curly brace of match
the variable ext
holding the extension
is not in scope any more. So anything you want to do with the extension must be inside the match
.
match path.extension() {
Some(ext) => println!("match: {:?} {:?}", path, ext),
None => println!("match: {:?} has no extension", path),
}
// ext is not in scope here
//println!("{}", ext);
Another way is to use a let Some()
statement with an else
part. In the else
part
we can report the lack of extension and then we need to terminate the current processing.
Because in our example we used a for-loop we terminate the current iteration by calling continue
.
If this was in a function, we would call return
.
The big advantage of this is that after the let
statement we have the ext
variable in scope:
let Some(ext) = path.extension() else {
println!("let: {:?} has no extension", path);
continue;
};
println!("let: {:?} {:?}", path, ext);
A third way is to combine the let
and match
statements. In this case we can set a default value
to be returned by the match
. In some cases this is preferable over skipping the rest of the code.
As that extension
method returns an instance of an OsStr
we also have to create the default value using that.
let ext = match path.extension() { Some(ext) => ext, None => OsStr::new(""), }; println!("let match: {:?} {:?}", path, ext);
The previous match
statement can be actually simplified to a call to unwrap_or_default:
let ext = path.extension().unwrap_or_default();
println!("default: {:?} {:?}", path, ext);
{% include file="examples/getting-file-extension/src/main.rs" %}
"hello.txt" "txt"
match: "hello.txt" "txt"
let match: "hello.txt" "txt"
default: "hello.txt" "txt"
let: "hello.txt" "txt"
match: "hello" has no extension
let match: "hello" ""
default: "hello" ""
let: "hello" has no extension
match: "main.rs" "rs"
let match: "main.rs" "rs"
default: "main.rs" "rs"
let: "main.rs" "rs"