Return a value from labeled loops - rust

We can return a value from loops with break, for example:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
And we can break the outer loops using labels, but is there a way to also return a value while breaking outer loops?
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up; // <-- Want to retun a value here
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}

Loops in Rust are expressions, and while most loops evaluate to (), you can break with a value, including breaking to outer labels. In the following example, this is accomplished by break 'counting_up Some(1) and assigning the loop's value via let foo: Option<usize> = 'counting_up: loop {
Notice that the compiler will enforce that all control flow paths evaluate to the same type (that is, the type of foo). If your loop does not produce a value in all cases, you can simply use Option, as in the example below.
fn main() {
let mut count = 0;
// Assign the loop's value to `foo`
let foo: Option<usize> = 'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
// Value found
break 'counting_up Some(1);
}
if count == 3 {
// Break early, assign `None` to `foo`
break 'counting_up None;
}
remaining -= 1;
}
count += 1;
if count >= 42 {
// break from 'counting_loop, assign `None` to `foo`
break None;
}
};
println!("End count = {foo:?}");
}

Related

Short-circuit iterator once condition is met

I am trying to write an iterator which conditionally uses elements in a separate iterator. In my example, the separate iterator should increment the sum variable. Once another condition is met *n == 4, the iterator should stop checking the condition and assume rest of elements are increments for the sum variable. I have the following working example:
fn conditional(n: &i64) -> bool {
// a lot of code here which is omitted for brevity
n % 2 == 0
}
fn main() {
let buf = vec![1,2,3,4,5,6];
let mut sum = 0;
let mut iter = buf.iter();
while let Some(n) = iter.next() {
if conditional(n) {
sum += n;
}
if *n == 4 {
// end of file - assume rest of elements are `conditional`
break;
}
};
// rest of elements [5,6]
for n in iter {
sum += n;
}
println!("sum (2+4+5+6): {:?}", sum);
}
output:
sum (2+4+5+6): 17
playground link
I would rather write the same thing with a single iterator using something like flat_map:
fn conditional(n: &i64) -> bool {
// a lot of code here which is omitted for brevity
n % 2 == 0
}
fn main() {
let buf = vec![1,2,3,4,5,6];
let mut sum = 0;
let mut terminate = false;
buf.iter().flat_map(|n| {
if *n == 4 {
// hard terminate here - return Some(n) for rest of iterator [5,6]
terminate = true;
return Some(n);
}
if terminate {
return Some(n);
}
if conditional(n) {
return Some(n);
}
None // odd
})
.for_each(|n| {
sum += n;
});
println!("sum (2+4+5+6): {:?}", sum);
}
output:
sum (2+4+5+6): 17
playground link
Is there a way to write this in a more concise manner? I want to short-circuit the iterator once the *n == 4 condition is reached.
There are many ways to solve this.
Here are a couple:
fn conditional(n: &i64) -> bool {
// a lot of code here which is omitted for brevity
n % 2 == 0
}
fn main() {
let buf = vec![1, 2, 3, 4, 5, 6];
let sum = buf
.iter()
.fold((0, false), |(mut sum, mut terminate), value| {
if *value == 4 {
terminate = true;
}
if terminate || conditional(value) {
sum += *value;
}
(sum, terminate)
})
.0;
println!("sum (2+4+5+6): {:?}", sum);
}
sum (2+4+5+6): 17
Or using filter and a stateful closure:
fn conditional(n: &i64) -> bool {
// a lot of code here which is omitted for brevity
n % 2 == 0
}
fn main() {
let buf = vec![1, 2, 3, 4, 5, 6];
let sum: i64 = buf
.iter()
.filter({
let mut terminate = false;
move |&value| {
terminate || {
if *value == 4 {
terminate = true;
}
conditional(value)
}
}
})
.sum();
println!("sum (2+4+5+6): {:?}", sum);
}
sum (2+4+5+6): 17
You can use filter():
buf.iter().filter(|n| {
if **n == 4 {
terminate = true;
}
terminate || conditional(n)
})
And sum() instead of for_each():
let sum = buf
.iter()
.filter(|n| {
if **n == 4 {
terminate = true;
}
terminate || conditional(n)
})
.sum::<i64>();

Is there a way to update a string in place in rust?

You can also consider this as, is it possible to URLify a string in place in rust?
For example,
Problem statement: Replace whitespace with %20
Assumption: String will have enough capacity left to accommodate new characters.
Input: Hello how are you
Output: Hello%20how%20are%20you
I know there are ways to do this if we don't have to do this "in place". I am solving a problem that explicitly states that you have to update in place.
If there isn't any safe way to do this, is there any particular reason behind that?
[Edit]
I was able to solve this using unsafe approach, but would appreciate a better approach than this. More idiomatic approach if there is.
fn space_20(sentence: &mut String) {
if !sentence.is_ascii() {
panic!("Invalid string");
}
let chars: Vec<usize> = sentence.char_indices().filter(|(_, ch)| ch.is_whitespace()).map(|(idx, _)| idx ).collect();
let char_count = chars.len();
if char_count == 0 {
return;
}
let sentence_len = sentence.len();
sentence.push_str(&"*".repeat(char_count*2)); // filling string with * so that bytes array becomes of required size.
unsafe {
let bytes = sentence.as_bytes_mut();
let mut final_idx = sentence_len + (char_count * 2) - 1;
let mut i = sentence_len - 1;
let mut char_ptr = char_count - 1;
loop {
if i != chars[char_ptr] {
bytes[final_idx] = bytes[i];
if final_idx == 0 {
// all elements are filled.
println!("all elements are filled.");
break;
}
final_idx -= 1;
} else {
bytes[final_idx] = '0' as u8;
bytes[final_idx - 1] = '2' as u8;
bytes[final_idx - 2] = '%' as u8;
// final_idx is of type usize cannot be less than 0.
if final_idx < 3 {
println!("all elements are filled at start.");
break;
}
final_idx -= 3;
// char_ptr is of type usize cannot be less than 0.
if char_ptr > 0 {
char_ptr -= 1;
}
}
if i == 0 {
// all elements are parsed.
println!("all elements are parsed.");
break;
}
i -= 1;
}
}
}
fn main() {
let mut sentence = String::with_capacity(1000);
sentence.push_str(" hello, how are you?");
// sentence.push_str("hello, how are you?");
// sentence.push_str(" hello, how are you? ");
// sentence.push_str(" ");
// sentence.push_str("abcd");
space_20(&mut sentence);
println!("{}", sentence);
}
An O(n) solution that neither uses unsafe nor allocates (provided that the string has enough capacity), using std::mem::take:
fn urlify_spaces(text: &mut String) {
const SPACE_REPLACEMENT: &[u8] = b"%20";
// operating on bytes for simplicity
let mut buffer = std::mem::take(text).into_bytes();
let old_len = buffer.len();
let space_count = buffer.iter().filter(|&&byte| byte == b' ').count();
let new_len = buffer.len() + (SPACE_REPLACEMENT.len() - 1) * space_count;
buffer.resize(new_len, b'\0');
let mut write_pos = new_len;
for read_pos in (0..old_len).rev() {
let byte = buffer[read_pos];
if byte == b' ' {
write_pos -= SPACE_REPLACEMENT.len();
buffer[write_pos..write_pos + SPACE_REPLACEMENT.len()]
.copy_from_slice(SPACE_REPLACEMENT);
} else {
write_pos -= 1;
buffer[write_pos] = byte;
}
}
*text = String::from_utf8(buffer).expect("invalid UTF-8 during URL-ification");
}
(playground)
Basically, it calculates the final length of the string, sets up a reading pointer and a writing pointer, and translates the string from right to left. Since "%20" has more characters than " ", the writing pointer never catches up with the reading pointer.
Is it possible to do this without unsafe?
Yes like this:
fn main() {
let mut my_string = String::from("Hello how are you");
let mut insert_positions = Vec::new();
let mut char_counter = 0;
for c in my_string.chars() {
if c == ' ' {
insert_positions.push(char_counter);
char_counter += 2; // Because we will insert two extra chars here later.
}
char_counter += 1;
}
for p in insert_positions.iter() {
my_string.remove(*p);
my_string.insert(*p, '0');
my_string.insert(*p, '2');
my_string.insert(*p, '%');
}
println!("{}", my_string);
}
Here is the Playground.
But should you do it?
As discussed for example here on Reddit this is almost always not the recommended way of doing this, because both remove and insert are O(n) operations as noted in the documentation.
Edit
A slightly better version:
fn main() {
let mut my_string = String::from("Hello how are you");
let mut insert_positions = Vec::new();
let mut char_counter = 0;
for c in my_string.chars() {
if c == ' ' {
insert_positions.push(char_counter);
char_counter += 2; // Because we will insert two extra chars here later.
}
char_counter += 1;
}
for p in insert_positions.iter() {
my_string.remove(*p);
my_string.insert_str(*p, "%20");
}
println!("{}", my_string);
}
and the corresponding Playground.

Why is a return type of () expected even though it is set to bool?

In a function that is declared to return bool:
pub fn is_palindrome(num: u64) -> bool {
let mut digits = Vec::new();
let mut temp = num;
loop {
digits.push(temp % 10);
temp /= 10;
if temp == 0 {
break;
}
}
for i in 0..digits.len() / 2 {
if digits[i] != digits[digits.len() - i] {
false // HERE I GET THE ERROR !!!!!!!
}
}
true
}
I get an error while compiling:
error[E0308]: mismatched types
--> src/lib.rs:13:13
|
13 | false
| ^^^^^ expected (), found bool
|
= note: expected type `()`
found type `bool`
Why is this happening, and how to fix the problem?
The issue is because you have an early return and not placing the return keyword before it, as shown below:
if digits[i] != digits[digits.len() - i] {
return false
}
This is because all functions evaluate the last line as a return value. If you want to return before going through the last line, you should add the return keyword.
An additional input here is to refactor your code so that it only returns once:
pub fn is_palindrome(num: u64) -> bool {
let mut digits = Vec::new();
let mut temp = num;
let mut retval = true;
loop {
digits.push(temp % 10);
temp /= 10;
if temp == 0 {
break;
}
}
for i in 0..digits.len() / 2 {
if digits[i] != digits[digits.len() - i] {
retval = false; // you might want to put a break here so that it exits the loop after finding the first issue
}
}
retval
}

println! outside of loop is an unreachable statement, how to use a println in a "reachable" way

How can I print the frequencies HashSet after the loop terminates? Problem seems to be there is no guarantee the loop terminates.
use std::collections::HashSet;
use std::fs;
fn main() {
let f = fs::read_to_string("input.txt").expect("Unable to open file");
let mut total = 0;
let mut frequencies = HashSet::new();
frequencies.insert(0);
loop {
for line in f.lines() {
let line_trimmed = line.trim();
let something = line_trimmed.parse::<i32>().unwrap();
total += something;
// println!("{:?}",total);
if frequencies.contains(&total) {
println!("duplicated found {:?}", total);
return;
}
frequencies.insert(total);
}
}
println!("duplicated found {:?}", frequencies);
}
println!("duplicated found {:?}", frequencies); is unreachable because, when you are calling return, you are returning out of the main function.
Change loop to a while, and set a variable to exit the while loop.
let mut duplicated_found = false;
while !duplicated_found {
for line in f.lines() {
let line_trimmed = line.trim();
let something = line_trimmed.parse::<i32>().unwrap();
total += something;
// println!("{:?}",total);
if frequencies.contains(&total) {
println!("duplicated found {:?}", total);
duplicated_found = true;
break; // This breaks out of the for loop
}
frequencies.insert(total);
}
}
println!("duplicated found {:?}", frequencies);
OR you can use labels to specify which loop you want to break out of:
'outer: loop {
for line in f.lines() {
let line_trimmed = line.trim();
let something = line_trimmed.parse::<i32>().unwrap();
total += something;
// println!("{:?}",total);
if frequencies.contains(&total) {
println!("duplicated found {:?}", total);
break 'outer; // This breaks out of the outer loop
}
frequencies.insert(total);
}
}
println!("duplicated found {:?}", frequencies);

Why does a variable assignment in a loop before a continue statement count as never read?

On the following code:
fn main() {
let mut i: Option<i32> = None;
let mut cond = true;
while true && i.map_or(true, |x| x < 10) {
if cond {
cond = false;
i = Some(0);
continue;
}
i = Some(5);
}
}
I get the warning:
warning: value assigned to `i` is never read
--> src/lib.rs:8:13
|
8 | i = Some(0);
| ^
|
= note: #[warn(unused_assignments)] on by default
This is very minimized (so please ignore for the moment that it'll loop forever), but shows the issue: the compiler seems to think that i from inside the if is overwritten by the outer assignment, which is clearly not the case due to the continue.
Am I missing something and have I introduced a bug of some description into the program, or is it the compiler's mistake?
The compiler is not well aware of the flow break controls because of a bug. If you can, you should use an expression oriented syntax that is more idiomatic:
fn main() {
let mut i = None;
let mut cond = true;
while true && i.map_or(true, |x| x < 10) {
i = if cond {
cond = false;
Some(0)
} else {
Some(5)
}
}
}
or create a custom iterator.

Resources