How to avoid "_" case in matches on modular numbers - rust

This smells bad:
let millis = time % 1000;
match millis {
0..=199 => do_something(),
200..=599 => do_something_else(),
600..=999 => do_something_altogether_different(),
_ => panic!("There are only 1,000 ms in a second."),
}
The "_" case will never be called, as millis is time % 1000.
How can I rewrite this so that I only have three branches?
Ideally, I'd also like to remove the duplication that is 199/200, 599/600 and 999/1000/0.
If the best solution is not a match I'm happy to use some other control structure.

Rust uses the type for matching, so it doesn't know the bounds as per your logic. But you do.
For being explicit you can use unreachable!:
Indicates unreachable code.
This is useful any time that the compiler can’t determine that some
code is unreachable. For example:
Match arms with guard conditions. Loops that dynamically terminate.
Iterators that dynamically terminate. If the determination that the
code is unreachable proves incorrect, the program immediately
terminates with a panic!.
let millis = time % 1000;
match millis {
0..=199 => do_something(),
200..=599 => do_something_else(),
600..=999 => do_something_altogether_different(),
_ => unreachable!("There are only 1,000 ms in a second."),
}
Otherwise you can consider that the last branch is the default one:
let millis = time % 1000;
match millis {
0..=199 => do_something(),
200..=599 => do_something_else(),
_ => do_something_altogether_different(),
}

I would use a simple if because you have one condition per case and only a small number of cases:
if millis < 200 {
do_something()
} else if millis < 600 {
do_something_else()
} else {
do_something_altogether_different()
}
If you want to use match you could also use if-guards in it:
match millis {
x if x < 200 => do_something(),
x if x < 600 => do_something_else(),
_ => do_something_altogether_different(),
}
Both remove the duplication of 199/200...

You can modify one of the arms so that the compiler understands it to include the case that cannot happen, by making one of the ranges unbounded. This could be considered the match version of the if chain option:
fn foo(time: u32) {
let millis = time % 1000;
match millis {
0..=199 => todo!(),
200..=599 => todo!(),
600.. => todo!(),
}
}
However, I would choose this option only in code that's as obviously-correct as this example; in more complex cases I'd prefer the version with an _ => unreachable!() since that will catch any bugs resulting in actually out-of-range values. (In fact, there's such a possible hidden situation in code very similar to this! If the input is a signed integer, time % 1000 might be negative, which is out of range. Though that'd make the above not compile for not covering those patterns.)

This is just an improvement on Kevin Reid's answer, which removes the duplication by reversing the order:
let millis: u64 = time % 1000;
match millis {
600.. => todo!(),
200.. => todo!(),
0.. => todo!(),
}
We could also leave out one more number, but I'm undecided as to whether this is an improvement.
let millis: u64 = time % 1000;
match millis {
600.. => todo!(),
200.. => todo!(),
_ => todo!(),
}

Related

Reduce nested match statements with unwrap_or_else in Rust

I have a rust program that has multiple nested match statements as shown below.
match client.get(url).send() {
Ok(mut res) => {
match res.read_to_string(&mut s) {
Ok(m) => {
match get_auth(m) {
Ok(k) => k,
Err(_) => return Err(“a”);
}
},
Err(_) => {
return Err(“b”);
}
}
},
Err(_) => {
return Err(“c”);
},
};
All the variables k and m are of type String.I am looking for a way to make the code more readable by removing excessive nested match statements keeping the error handling intact since both the output and the error types are important for the problem.Is it possible to achieve this by unwrap_or_else?
The .map_err() utility converts a Result to have a new error type, leaving the success type alone. It accepts a closure that consumes the existing error value and returns the new one.
The ? operator will early-return the error in the Err case, and unwrap in the Ok case.
Combining these two allows you to express this same flow succinctly:
get_auth(
client.get(url).send().map_err(|_| "c")?
.read_to_string(&mut s).map_err(|_| "b")?
).map_err(|_| "a")?
(I suspect that you actually want to pass s to get_auth() but that's not what the code in your question does, so I'm choosing to represent the code you posted instead of imaginary code that I'm guessing about.)

How do I evaluate and capture a function for later use in a loop?

I have the following function:
pub fn generate_mock_balances(interval: &Interval, n: &i32) -> Vec<HistoricBalance> {
let time = Utc::now();
let ticks = (1..*n).map(|v| v as i64);
let mut mocks = vec![];
for tick in ticks {
let historic_time = match interval {
Interval::Hour => time - Duration::minutes(tick),
Interval::Day => time - Duration::hours(tick),
Interval::Week => time - Duration::days(tick),
Interval::Month => time - Duration::weeks(tick),
Interval::Year => time - Duration::weeks(4 * tick),
};
mocks.push(HistoricBalance {
time: historic_time.timestamp().to_string(),
balance: (1499 * tick).to_string(),
});
}
mocks
}
I feel like the switch case within the loop is not efficient and that there must be a way to select the appropriate Duration function once and store it for use in the loop, instead of doing the check at every iteration of the loop.
I've taken a look at the documentation for closures and while I instinctively feel it's the way to go it seems more suited towards speeding up slow calculations by storing the outcome of said calculation for re-use. How do I do this?
You can match the interval once and save the function in a variable:
let duration_fn = match interval {
Interval::Hour => Duration::minutes,
Interval::Day => Duration::hours,
Interval::Week => Duration::days,
Interval::Month => Duration::weeks,
Interval::Year => |tick: i64| Duration::weeks(4 * tick),
};
and use the function in the loop:
let historic_time = time - duration_fn(tick);
If you want you could simplify the creation of ticks:
let ticks = 1i64..*n as i64;
playground

What is the difference between the switch and match syntax?

Some languages have a switch expression/statement and some have a match statement. Is there a difference in this semantically, or is it just a different syntax for something that is fundamentally the same.
For example:
Rust has match:
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => 25,
}
(Taken from https://doc.rust-lang.org/book/ch06-02-match.html#the-match-control-flow-operator.)
Java has switch:
switch coin {
case Penny:
return 1;
break;
case Nickel:
return 5;
break;
case Dime:
return 10;
break;
case Quarter:
return 25;
break;
}
(A piece of equivalent code.)
Caveat: this varies wildly depending on the language, of course.
Here I'll work with the Java switch statement, since it's a commonly-used language and its switch semantics seem roughly representative.
A few key differences are:
match is exhaustive (i.e. you have to be able to prove to the compiler that exactly one branch is matched)
match is an expression (although arguably this is more a Rust feature than a match feature), i.e.:
let x = 123;
let s = match x {
0 => "zero",
1 => "one",
_ => "something else",
};
println!("{}", s); // prints "something else"
match performs destructuring:
let x = Some(123);
let s = match x {
None => "nothing".to_string(),
Some(x) => format!("the number: {}", x),
};
println!("{}", s); // prints "the number: 123"
However, you really should take a look at the docs for match and compare them to the docs for the relevant switch feature in another language. These are just the obvious differences when comparing against C-like switches.

Gstreamer Editing Service rust bindings error while setting render settings

I'm very new to gstreamer and rust and am trying to render a video made from sections of other videos. Based on the docs, gstreamer-rs examples, and this question about doing the same thing in python, I think my code looks pretty good, but throws errors.
This is my code:
use gstreamer as gst;
use gstreamer::{ElementExt, ElementExtManual, GstObjectExt};
use gstreamer_editing_services as ges;
use gstreamer_editing_services::{GESPipelineExt, LayerExt, TimelineExt};
use gstreamer_pbutils as gst_pbutils;
use gstreamer_pbutils::{EncodingProfileBuilder};
pub fn clip_video() {
match gst::init() {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
match ges::init() {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
let timeline = ges::Timeline::new_audio_video();
let layer = timeline.append_layer();
let pipeline = ges::Pipeline::new();
match pipeline.set_timeline(&timeline) {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
let video_profile = gst_pbutils::EncodingVideoProfileBuilder::new()
.name("h.264")
.description("h.264-profile")
.format(&gst::caps::Caps::new_simple("video/x-h264", &[]))
.build()
.unwrap();
let audio_profile = gst_pbutils::EncodingAudioProfileBuilder::new()
.name("mp3")
.description("mp3-profile")
.format(&gst::caps::Caps::new_simple(
"audio/mpeg",
&[("mpegversion", &"1"), ("layer", &"3")],
))
.build()
.unwrap();
let contianer_profile = gst_pbutils::EncodingContainerProfileBuilder::new()
.name("default-mp4-profile")
.description("mp4-with-h.264-mp3")
.format(&gst::caps::Caps::new_simple(
"video/quicktime",
&[("variant", &"iso")],
))
.enabled(true)
.add_profile(&video_profile)
.add_profile(&audio_profile)
.build()
.unwrap();
let asset = ges::UriClipAsset::request_sync("file:///home/ryan/repos/auto-highlighter-processing-service/input/test-video.mp4").expect("Failed to create asset");
match layer.add_asset(
&asset,
0 * gst::SECOND,
10 * gst::SECOND,
10 * gst::SECOND,
ges::TrackType::CUSTOM,
) {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
match pipeline.set_render_settings("file:///home/ryan/repos/auto-highlighter-processing-service/output/test-video.mp4", &contianer_profile){
Err(e) => eprintln!("{:?}", e),
_ => (),
}
match pipeline.set_mode(ges::PipelineFlags::RENDER) {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
match pipeline.set_state(gst::State::Playing) {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
let bus = pipeline.get_bus().unwrap();
for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => break,
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.get_src().map(|s| s.get_path_string()),
err.get_error(),
err.get_debug()
);
break;
}
_ => (),
}
}
}
The errors that I am getting:
BoolError { message: "Failed to set render settings", filename: "/home/ryan/.cargo/registry/src/github.com-1ecc6299db9ec823/gstreamer-editing-services-0.16.5/src/auto/pipeline.rs", function: "gstreamer_editing_services::auto::pipeline", line: 228 }
StateChangeError
I'm struggling to find what to do about these errors or what the problem could be. From what I know I'm using the set_render_settings() and set_mode() functions correctly.
I didn't try running your code, but one problem I found when reading was the following
.format(&gst::caps::Caps::new_simple(
"audio/mpeg",
&[("mpegversion", &"1"), ("layer", &"3")],
))
The "mpegversion" and "layer" fields of the caps are not strings but integers. If you use them as such it should work (or at least work better)
.format(&gst::caps::Caps::new_simple(
"audio/mpeg",
&[("mpegversion", &1i32), ("layer", &3i32)],
))
Everything else looks correct to me.
You can find more details about such errors by making use of the GStreamer debugging system. You can enable that via the GST_DEBUG environment variable, e.g. by setting that to 6.
Although this answer is over a year late, I thought I'd post anyway, as examples in Rust for GES are sparse, with only a single (though good) example of applying a 'agingtv' effect on the gstreamer-rs repo. Additionally, the OP's sample code above will result in rendering 10 seconds of black video, and does not, as the OP mentioned, result in the desired output.
Using the example listed in the original question above:
On gstreamer-rs 0.19:
Build video profile (note the caps now must be passed via builder() ):
let video_profile = gstreamer_pbutils::EncodingVideoProfile::builder(
&gst::Caps::builder("video/x-h264").build(),
)
.name("video_profile")
.build();
Build the Audio profile:
let audio_profile = gstreamer_pbutils::EncodingAudioProfile::builder(
&gstreamer::Caps::new_simple("audio/x-aac", &[]),
)
.name("audio_profile")
.build();
Build the Container profile:
let container_profile = gstreamer_pbutils::EncodingContainerProfile::builder(
&gstreamer::Caps::new_simple("video/x-matroska", &[]),
)
.name("container_profile")
.add_profile(&audio_profile)
.add_profile(&video_profile)
.build();
Note: as an alternative you can build the whole encoding profile in one go from the DiscovererInfo if you ran the gst-discover on the media first. This will result in an output file very similar to the input file in it's encoding settings.
let encoding_profile =
gstreamer_pbutils::EncodingProfile::from_discoverer(&m_info.discover_info)?;
The following example will clip the video, add a transition, and merge in an additional clip to fade to:
let timeline = ges::Timeline::new_audio_video();
timeline.set_auto_transition(true);
let layer = timeline.append_layer();
let pipeline = ges::Pipeline::new();
pipeline.set_timeline(&timeline)?;
let audio_profile = gstreamer_pbutils::EncodingAudioProfile::builder(
&gstreamer::Caps::new_simple("audio/x-aac", &[]),
)
.name("audio_profile")
.build();
let video_profile = gstreamer_pbutils::EncodingVideoProfile::builder(
&gst::Caps::builder("video/x-h264").build(),
)
.name("video_profile")
.build();
let container_profile = gstreamer_pbutils::EncodingContainerProfile::builder(
&gstreamer::Caps::new_simple("video/x-matroska", &[]),
)
.name("container_profile")
.add_profile(&audio_profile)
.add_profile(&video_profile)
.build();
/* alternatively
let encoding_profile = gstreamer_pbutils::EncodingProfile::from_discoverer(&m_info.discover_info)?;
*/
/* original video */
let clip = ges::UriClip::new("file:///home/ryan/repos/auto-highlighter-processing-service/input/test-video.mp4")?;
layer.add_clip(&clip)?;
clip.set_inpoint(gst::ClockTime::from_seconds(0));
clip.set_duration(gst::ClockTime::from_seconds(10));
/* video to transition to with a fade */
let clip_transition_to = ges::UriClip::new("/some/2/second/video/file.mp4")?;
clip_transition_to.set_start(gst::ClockTime::from_seconds(9)); //this should overlap the original video clip, but not completely
clip_transition_to.set_inpoint(gst::ClockTime::from_seconds(0));
clip_transition_to.set_duration(gst::ClockTime::from_seconds(2));
layer.add_clip(&clip_transition_to)?;
pipeline.set_render_settings("file:///home/ryan/repos/auto-highlighter-processing-service/output/test-video.mp4", &container_profile)?; //or &encoding_profile
pipeline.set_mode(ges::PipelineFlags::RENDER)?;
pipeline.set_state(gst::State::Playing)?;
let bus = pipeline
.bus()
.expect("Pipeline without bus. Shouldn't happen!");
for msg in bus.iter_timed(gst::ClockTime::NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => break,
MessageView::Error(err) => {
pipeline.set_state(gst::State::Null)?;
match err.details() {
Some(details) if details.name() == "error-details" => details
.get::<&ErrorValue>("error")
.unwrap()
.clone()
.0
.lock()
.unwrap()
.take()
.map(Result::Err)
.expect("error-details message without actual error"),
_ => Err({
let err_src = msg
.src()
.map(|s| String::from(s.path_string()))
.unwrap_or_else(|| String::from("None"));
log!(
Level::Error,
"A GStreamer Error was Encountered {}",
&err_src
);
ErrorMessage {
src: err_src,
error: err.error().to_string(),
debug: err.debug(),
source: err.error(),
}
.into()
}),
}?;
}
MessageView::StateChanged(state_changed) => {
// if state_changed.src().map(|s| s == decodebin).unwrap_or(false)
// && state_changed.current() == gst::State::Playing
// {
// // Generate a dot graph of the pipeline to GST_DEBUG_DUMP_DOT_DIR if defined
// let bin_ref = decodebin.downcast_ref::<gst::Bin>().unwrap();
// bin_ref.debug_to_dot_file(gst::DebugGraphDetails::all(), "PLAYING");
// }
let msg = format!(
"State changed from {:?}: {:?} -> {:?} ({:?})",
state_changed.src().map(|s| s.path_string()),
state_changed.old(),
state_changed.current(),
state_changed.pending()
);
log!(Level::Debug, "{}", msg)
}
_ => (),
}
}
let log_msg = "Play run complete, changing state...";
log!(Level::Info, "{}", &log_msg);
pipeline.set_state(gst::State::Null)?;
The result will be a 10 second video, with a fade out to a 2 second video (e.g. something that says "the end", etc).
I hope this helps someone. It took a some reading and research to achieve the desired effect, and hopefully this will help someone else on the same journey.
gstreamer is excellent software and, although it's been a bit of work to get things functional, it's been great to work with the rust bindings.

What is the correct & idiomatic way to check if a string starts with a certain character in Rust?

I want to check whether a string starts with some chars:
for line in lines_of_text.split("\n").collect::<Vec<_>>().iter() {
let rendered = match line.char_at(0) {
'#' => {
// Heading
Cyan.paint(*line).to_string()
}
'>' => {
// Quotation
White.paint(*line).to_string()
}
'-' => {
// Inline list
Green.paint(*line).to_string()
}
'`' => {
// Code
White.paint(*line).to_string()
}
_ => (*line).to_string(),
};
println!("{:?}", rendered);
}
I've used char_at, but it reports an error due to its instability.
main.rs:49:29: 49:39 error: use of unstable library feature 'str_char': frequently replaced by the chars() iterator, this method may be removed or possibly renamed in the future; it is normally replaced by chars/char_indices iterators or by getting the first char from a subslice (see issue #27754)
main.rs:49 let rendered = match line.char_at(0) {
^~~~~~~~~~
I'm currently using Rust 1.5
The error message gives useful hints on what to do:
frequently replaced by the chars() iterator, this method may be removed or possibly renamed in the future; it is normally replaced by chars/char_indices iterators or by getting the first char from a subslice (see issue #27754)
We could follow the error text:
for line in lines_of_text.split("\n") {
match line.chars().next() {
Some('#') => println!("Heading"),
Some('>') => println!("Quotation"),
Some('-') => println!("Inline list"),
Some('`') => println!("Code"),
Some(_) => println!("Other"),
None => println!("Empty string"),
};
}
Note that this exposes an error condition you were not handling! What if there was no first character?
We could slice the string and then pattern match on string slices:
for line in lines_of_text.split("\n") {
match &line[..1] {
"#" => println!("Heading"),
">" => println!("Quotation"),
"-" => println!("Inline list"),
"`" => println!("Code"),
_ => println!("Other")
};
}
Slicing a string operates by bytes and thus this will panic if your first character isn't exactly 1 byte (a.k.a. an ASCII character). It will also panic if the string is empty. You can choose to avoid these panics:
for line in lines_of_text.split("\n") {
match line.get(..1) {
Some("#") => println!("Heading"),
Some(">") => println!("Quotation"),
Some("-") => println!("Inline list"),
Some("`") => println!("Code"),
_ => println!("Other"),
};
}
We could use the method that is a direct match to your problem statement, str::starts_with:
for line in lines_of_text.split("\n") {
if line.starts_with('#') { println!("Heading") }
else if line.starts_with('>') { println!("Quotation") }
else if line.starts_with('-') { println!("Inline list") }
else if line.starts_with('`') { println!("Code") }
else { println!("Other") }
}
Note that this solution doesn't panic if the string is empty or if the first character isn't ASCII. I'd probably pick this solution for those reasons. Putting the if bodies on the same line as the if statement is not normal Rust style, but I put it that way to leave it consistent with the other examples. You should look to see how separating them onto different lines looks.
As an aside, you don't need collect::<Vec<_>>().iter(), this is just inefficient. There's no reason to take an iterator, build a vector from it, then iterate over the vector. Just use the original iterator.

Resources