Default value from Option<String> - rust

How can I return a default value from an Option<&String>?
This is my sample/minimal code:
fn main() {
let map = std::collections::HashMap::<String, String>::new();
let result = map.get("").or_else(|| Some("")).unwrap(); // <== I tried lots of combinations
println!("{}", result);
}
I know I could do something like this...
let value = match result {
Some(v) => v,
None => "",
};
... but I want to know if it is possible to implement it in a one-liner with or_else or unwrap_or_else?
(It is important to make the default value lazy, so it does not get computed if it is not used)
These are some of the compiler suggestions I tried (I can put them all because SO won't allow me):
7 | let result = map.get("").or_else(|| Some("") ).unwrap();
| ^^ expected struct `String`, found `str`
.
7 | let result = map.get("").or_else(|| Some(&"".to_string()) ).unwrap();
| ^^^^^^--------------^
| | |
| | temporary value created here
| returns a value referencing data owned by the current function
.
7 | let result = map.get("").or_else(|| Some(String::new()) ).unwrap();
| ^^^^^^^^^^^^^
| |
| expected `&String`, found struct `String`
|
help: consider borrowing here: `&String::new()`
.
7 | let result = map.get("").or_else(|| Some(&String::new()) ).unwrap();
| ^^^^^^-------------^
| | |
| | temporary value created here
| returns a value referencing data owned by the current function
.
and also
6 | let result = map.get("").unwrap_or_else(|| ""); // I tried lots
| ^^ expected struct `String`, found `str`
|
= note: expected reference `&String`
found reference `&'static str`

If you really need a &String as the result, you may create a String for the default value with lifetime that's long enough.
fn main() {
let map = std::collections::HashMap::<String, String>::new();
let default_value = "default_value".to_string();
let result = map.get("").unwrap_or(&default_value);
println!("{}", result);
}
If the default value is a compile-time fixed value, the allocation of default_value can be avoided by using &str instead.
fn main() {
let map = std::collections::HashMap::<String, String>::new();
let result = map.get("")
.map(String::as_str)
.unwrap_or("default_value");
println!("{}", result);
}

How can I return a default value from an Option<&String>?
It's not trivial because as you've discovered ownership gets in the way, as you need an actual String to create an &String. The cheap and easy solution to that is to just have a static empty String really:
static DEFAULT: String = String::new();
fn main() {
let map = std::collections::HashMap::<String, String>::new();
let result = map.get("").unwrap_or(&DEFAULT); // <== I tried lots of combinations
println!("{}", result);
}
String::new is const since 1.39.0, and does not allocate, so this works fine. If you want a non-empty string as default value it's not as good a solution though.
The cleaner and more regular alternative is to "downgrade" (or upgrade, depending on the POV) the &String to an &str:
let result = map.get("").map(String::as_str).unwrap_or("");
or
let result = map.get("").map(|s| &**s).unwrap_or("");
it's really not like you're losing anything here, as &String is not much more capable than &str (it does offer a few more thing e.g. String::capacity, but for the most part it exists on genericity grounds e.g. HashMap::<K, V>::get returns an &V, so if you store a String you get an &String makes sense even though it's not always quite the thing you want most).

Related

Rust String::to_bytes | What does the Rust compiler mean here, exactly?

I am newbie in the Rust world.
As an exercise, this is the problem I am trying to solve:
fn main() {
let s = give_ownership();
println!("{}", s);
}
// Only modify the code below!
fn give_ownership() -> String {
let s = String::from("hello, world");
// Convert String to Vec
let _s = s.into_bytes();
s
}
I have gotten through. My solution works.
However, when I compile the exercise code-snippet above unchanged, I don't quite get what the compiler is telling me here, as a note below:
Compiling playground v0.0.1 (/playground)
error[E0382]: use of moved value: `s`
--> src/main.rs:12:5
|
9 | let s = String::from("hello, world");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
10 | // Convert String to Vec
11 | let _s = s.into_bytes();
| ------------ `s` moved due to this method call
12 | s
| ^ value used here after move
|
note: this function takes ownership of the receiver `self`, which moves `s`
My guess is that the note is about the function into_bytes(). The RustDoc says this about the function:
This consumes the String, so we do not need to copy its contents.
Could someone please elaborate on this?
into_bytes() takes self (i.e. an owned self, not a reference).
This means that it takes ownership of the string it's called on. It's conceptually the same as this:
fn main() {
let s = String::from("hello");
take_string(s);
println!("{s}"); // ERROR
}
fn take_string(s: String) {}
This is useful because it allows you to turn a String into a Vec<u8>, while reusing the allocation. A String is really just a Vec<u8> with the guarantee that the bytes are valid UTF-8.
So once you write let _s = s.into_bytes(), the data that was in s has now moved to _s, so you can't return s from your function. There's nothing there.
If you just want to return the string, you can just return String::from("stuff")

Rust: Error[E0277]: the trait bound `{integer}: SampleRange<_>` is not satisfied

I have a line of code that is in a for loop, and it's supposed to generate a random number from 0 to 2499. It is giving me problems.
let index = rand::thread_rng().gen_range(2499);
Full code for those who want to know:
fn generate_phrase () -> String {
let mut phrase = String::new();
let mut file = File::open("words.txt").expect("Failed to open words.txt");
let mut contents = String::new();
file.read_to_string(&mut contents).expect("Failed to read words.txt");
let words: Vec<&str> = contents.split("\n").collect();
for _ in 0..8 {
let index = rand::thread_rng().gen_range(2499);
phrase.push_str(words[index]);
phrase.push(' ');
}
println!("Your phrase is: {:?}", phrase);
return phrase;
}
Error message:
error[E0277]: the trait bound `{integer}: SampleRange<_>` is not satisfied
--> src/crypto/crypto.rs:115:45
|
115 | let index = rand::thread_rng().gen_range(2499);
| --------- ^^^^ the trait `SampleRange<_>` is not implemented for `{integer}`
| |
| required by a bound introduced by this call
|
note: required by a bound in `gen_range`
--> C:\Users\Administrator\.cargo\registry\src\github.com-1ecc6299db9ec823\rand-0.8.5\src\rng.rs:132:12
|
132 | R: SampleRange<T>
| ^^^^^^^^^^^^^^ required by this bound in `gen_range
I know the problem, which is that the trait is not the right kind but I don't know how to convert the integer into the necessary trait: SampleRange<T>. I've looked on StackOverFlow and couldn't find an appropriate answer anywhere.
The SampleRange that it complains about can be either a Range or RangeInclusive, rather than just an upper-bound (see the "implementations" section in SampleRange to see which types implement the trait). All you need is to change that one line to look something like this:
let index = rand::thread_rng().gen_range(0..2499);

error[E0597]: borrowed value does not live long enough in While loop

I am really new to Rust, I am having trouble solving this error, but it only happens if I comment out the while statement , basicly I am asking values from the console and storing it in a HashMap:
use std::collections::HashMap;
use std::io;
fn main() {
let mut customers = HashMap::new();
let mut next_customer = true;
while next_customer {
let mut input_string = String::new();
let mut temp_vec = Vec::with_capacity(3);
let mut vec = Vec::with_capacity(2);
println!("Insert new customer f.e = customer id,name,address:");
io::stdin().read_line(&mut input_string);
input_string = input_string.trim().to_string();
for s in input_string.split(",") {
temp_vec.push(s);
}
vec.push(temp_vec[1]);
vec.push(temp_vec[2]);
let mut key_value = temp_vec[0].parse::<i32>().unwrap();
customers.insert(key_value, vec);
next_customer = false;
}
println!("DONE");
}
The code results in the error
error[E0597]: `input_string` does not live long enough
--> src/main.rs:14:18
|
14 | for s in input_string.split(",") {
| ^^^^^^^^^^^^ borrowed value does not live long enough
...
20 | customers.insert(key_value, vec);
| --------- borrow later used here
21 | next_customer = false;
22 | }
| - `input_string` dropped here while still borrowed
As others have said the problem lies with the lifetime and/or type of the values getting put into the customers map.
customers.insert(key_value, vec);
| --------- borrow later used here
Often this happens when the compiler has decided to give an object a type that you didn't expect. To find out what it's doing you can force the type, and see how it complains. Changing the code to:
let mut customers: HashMap<(),()> = HashMap::new();
Gives us two relevant errors:
20 | customers.insert(key_value, vec);
| ^^^^^^^^^ expected `()`, found `i32`
...
20 | customers.insert(key_value, vec);
| ^^^ expected `()`, found struct `std::vec::Vec`
|
= note: expected unit type `()`
found struct `std::vec::Vec<&str>`
So the type that the compiler wants to give our customers object is HashMap<i32, Vec<&str>>
The problem is that the &str lifetime has got to be inside the block as we don't store the Strings anywhere, and they can't have 'static lifetime since they're user input.
This means we probably want a HashMap<i32,Vec<String>>.
Changing the code to use one of those gives us an error about vec not having the right type: It's getting deduced as a Vec<&str>, but we want a Vec<String>.
We have two options.
Convert the vec to the right type just before we insert it into the map using customers.insert(key_value, vec.iter().map(|s| s.to_string()).collect()). (Though you may want to extract it to a variable for clarity).
Explicitly change the type of vec to Vec<String>
Option 1 "just works". While option 2 leads us down a path of making similar changes closer and closer to the read_line call.
Once you've decided on the fix in option 1, you can remove the manual type annotations that were added to work out the fix, if you find them overly noisy.
The issue is that you are passing around reference to underlying &str values that will get dropped. One way is to take the input string, trim and split it, then clone it going into the other vector.
let temp_vec: Vec<String> = input_string.trim().split(",").map(|t| t.to_string()).collect();
vec.push(temp_vec[1].clone());
vec.push(temp_vec[2].clone());

Why does a variable holding the result of Vec::get_mut not need to be mutable?

I have the following code:
fn main() {
let mut vec = Vec::new();
vec.push(String::from("Foo"));
let mut row = vec.get_mut(0).unwrap();
row.push('!');
println!("{}", vec[0])
}
It prints out "Foo!", but the compiler tells me:
warning: variable does not need to be mutable
--> src/main.rs:4:9
|
4 | let mut row = vec.get_mut(0).unwrap();
| ----^^^
| |
| help: remove this `mut`
Surprisingly, removing the mut works. This raises a few questions:
Why does this work?
Why doesn't this work when I use vec.get instead of vec.get_mut, regardless of whether I use let or let mut?
Why doesn't vec work in the same way, i.e. when I use let vec = Vec::new(), why can't I call vec.push()?
vec.get_mut(0) returns an Option<&mut String>, so when you unwrap that value you will have a mutable borrow of a String. Remember, that a let statement's left side is using pattern matching, so when your pattern is just a variable name you essentially say match whatever is on the right and call it name. Thus row matches against &mut String so it already is mutable.
Here's a much simpler and more straightforward example to illustrate the case (which you can try in the playground):
fn main() {
let mut x = 55i32;
dbg!(&x);
let y = &mut x; // <-- y's type is `&mut i32`
*y = 12;
dbg!(&x);
}

Borrowed value does not live long enough when creating a Vec

Editor's note: This question was asked before Rust 1.0. Since then, many functions and types have changed, as have certain language semantics. The code in the question is no longer valid, but the ideas expressed in the answers may be.
I'm trying to list the files in a directory and copy the filename to my own Vec. I've tried several solutions, but it always ends up with a problem of not being able to create long enough living variables. I don't understand my mistake.
fn getList(action_dir_path : &str) -> Vec<&str> {
let v = fs::readdir(&Path::new(action_dir_path))
.unwrap()
.iter()
.map(|&x| x.filestem_str().unwrap())
.collect();
return v;
}
Why does the compiler complain about "x" ? I don't care about x, I want the &str inside it and I thought &str were static.
I tried this way, but I got the same result with the compiler complaining about "paths" not living long enough.
fn getList2(action_dir_path : &str) -> Vec<&str> {
let paths = fs::readdir(&Path::new(action_dir_path)).unwrap();
let mut v : Vec<&str> = Vec::new();
for path in paths.iter(){
let aSlice = path.filestem_str().unwrap();
v.push(aSlice);
}
return v;
}
Here is the playground.
The most literal translation of your code that supports Rust 1.0 is this:
use std::{fs, path::Path, ffi::OsStr};
fn getList(action_dir_path: &str) -> Vec<&OsStr> {
let v = fs::read_dir(&Path::new(action_dir_path))
.unwrap()
.map(|x| x.unwrap().path().file_stem().unwrap())
.collect();
return v;
}
This produces the error messages:
Rust 2015
error[E0597]: borrowed value does not live long enough
--> src/lib.rs:6:18
|
6 | .map(|x| x.unwrap().path().file_stem().unwrap())
| ^^^^^^^^^^^^^^^^^ - temporary value only lives until here
| |
| temporary value does not live long enough
|
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 3:1...
--> src/lib.rs:3:1
|
3 | / fn getList(action_dir_path: &str) -> Vec<&OsStr> {
4 | | let v = fs::read_dir(&Path::new(action_dir_path))
5 | | .unwrap()
6 | | .map(|x| x.unwrap().path().file_stem().unwrap())
7 | | .collect();
8 | | return v;
9 | | }
| |_^
Rust 2018
error[E0515]: cannot return value referencing temporary value
--> src/lib.rs:6:18
|
6 | .map(|x| x.unwrap().path().file_stem().unwrap())
| -----------------^^^^^^^^^^^^^^^^^^^^^
| |
| returns a value referencing data owned by the current function
| temporary value created here
The problem comes from Path::file_stem. This is the signature:
pub fn file_stem(&self) -> Option<&OsStr>
This indicates that the method will return a borrowed reference to a OsStr. The PathBuf struct is the owner of the string. When you leave the method, there's nowhere left that owns the PathBuf, so it will be dropped. This means that any references into the PathBuf will no longer be valid. This is Rust preventing you from having references to memory that is no longer allocated, yay for Rust!
The easiest thing you can do is return a Vec<String>. String owns the string inside of it, so we don't need to worry about it being freed when we leave the function:
fn get_list(action_dir_path: &str) -> Vec<String> {
fs::read_dir(action_dir_path)
.unwrap()
.map(|x| {
x.unwrap()
.path()
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string()
})
.collect()
}
I also updated the style (at no charge!) to be more Rust-like:
Use snake_case for items
No space before the colon in type definitions
There's no reason to set a variable just to return it.
Don't use explicit return statements unless you are exiting from a function early.
There's no need to wrap the path in a Path.
However, I'm not a fan of all of the unwrapping. I'd write the function like this:
use std::{ffi::OsString, fs, io, path::Path};
fn get_list(action_dir_path: impl AsRef<Path>) -> io::Result<Vec<OsString>> {
fs::read_dir(action_dir_path)?
.map(|entry| entry.map(|e| e.file_name()))
.collect()
}
fn main() {
println!("{:?}", get_list("/etc"));
}
In addition to the changes above:
I use a generic type for the input path.
I return a Result to propagate errors to the caller.
I directly ask the DirEntry for the filename.
I leave the type as an OsString.
One small related point:
I thought &str were static.
&'static strs are static, but that's only one kind of &str. It can have any kind of lifetime.

Resources