I currently have a bunch of functions that do stuff and return nothing. Each function may run f_foo, f_bar, both, or neither before returning.
I'm thinking of moving those calls to f_foo and f_bar outside the functions and changing the return type to indicate whether or not to call f_foo or f_bar, both, or neither.
So far I've identified two possible ways to do this, using either an enum or a tuple:
use Thing::{*};
fn main() {
//method one
let x = f_1();
match x {
Foo(foo) => f_foo(foo),
Bar(bar) => f_bar(bar),
FooBar(foo,bar) => {f_foo(foo);f_bar(bar);},
Nothing => ()
}
//method two
let (foo, bar) = f_2();
if let Some(foo) = foo {
f_foo(foo);
}
if let Some(bar) = bar {
f_bar(bar);
}
}
fn f_1()-> Thing{
FooBar(5, false)
}
fn f_2() -> (Option<u16>, Option<bool>){
(Some(5), Some(false))
}
enum Thing {
Foo(u16),
Bar(bool),
FooBar(u16, bool),
Nothing,
}
fn f_foo(foo: u16){
println!("{}", foo);
}
fn f_bar(bar: bool){
println!("{}", bar);
}
The enum is definitely cleaner when creating the return value - less clutter from Some() everywhere, which is important to me because it is used in a lot of places.
However I find the if let statements that work with the tuple to be cleaner than the match statement with the enum.
Is there a way to get the best of both of these approaches, or something else I've overlooked?
In general, I would probably favor the second method, since the first one becomes clumsy when generalized to more than two functions.
But I can't think of a concrete use case at the moment — letting the functions call f_foo and f_bar seems good enough to me. Can you show more context? Sometimes, specific contexts call for specific solutions.
You could use an enum to represent the individual calls and an Iterator to represent a sequence of them: (Playground)
But there are more options, too, depending on what you are trying to accomplish. Two that come to mind are returning a closure or something that implements Drop.
use Thing::{*};
fn main() {
for x in f_3() {
match x {
Foo(foo) => f_foo(foo),
Bar(bar) => f_bar(bar),
}
}
}
fn f_3() -> impl IntoIterator<Item=Thing> {
vec![Foo(5), Bar(false)]
}
enum Thing {
Foo(u16),
Bar(bool),
}
fn f_foo(foo: u16){
println!("{}", foo);
}
fn f_bar(bar: bool){
println!("{}", bar);
}
The hybrid approach is pretty much what I was looking for.
The functions f_foo and f_bar are the only ones that will ever be needed and they will always occur in the same order: foo then bar, so generalizing to an iterator seems like overkill.
To answer L_F: Currently I have private functions in a library that call f_foo and f_bar, which are also private functions. But there is a need for a user defined function outside the library to be able to mimic this behavior, but I don't want f_foo and f_bar public, nor do I want a user defined function to break the implicit contract by calling them out of order, or more than once, thus the change to a return type.
That sounds like you're providing some kind of cleanup routine that might need to run after user code. Does it make sense to return an RAII guard instead that will do the cleanup when the value is dropped?
You might also want to tag MyThing with the #[must_use] attribute. That'll prevent callers from accidentally ignoring one that gets returned from a function (though they can still explicitly ignore it).
I have a very similar problem where I need to represent open/closed intervals ([a,b], [a,b), (a,b], (a,b) in mathematical notation) and I cannot easily decide between several possible implementations. They each have different pros & cons, forcing you to structure the rest of the code slightly differently, with different constructors and helper functions. In the end, I prefer the endpoint_kind, followed by wrapped, for ease of use, even if they take up more memory space, I think (because they need 2 tags instead of 1). I would like to know if some of you have opinions in this regard or suggestions.
Yes, thank you; I was aware of it, but the main difference is that in that case RangeBounds is a trait and the different kind of intervals are independent structures. I believe in my situation I cannot afford the dynamic dispatch (I need to work with heterogeneous collections of intervals of different kinds), so I need a single interval type with tags indicating its topological properties.
Effectively, using (Bound<T>, Bound<T>) to represent an interval, as here, is more or less equivalent to my wrapped version.
Lacking knowledge of what you are attempting, and thus the distribution frequency of the various potential endpoint checks and computations, I would start coding with your enumeration::Interval and plan to refactor once the pros and cons of each alternative became more obvious from your coding experience.
I'm implementing a type representing a finite union of intervals, with union, intersection, complement operations. The biggest complication is to deal with generic types that can be bounded (u8, characters, an enum...), unbounded (f64, with ±inf representing the endpoint of unbounded intervals), and mathematically unbounded but with bounded computer representatives (i32). I still have to figure out how to capture nicely all this possible variations with a single elegant type.
I also started with enumeration::Interval, but pattern matching on two intervals can become unwieldy. Imagine a function
that checks if the union of int1 and int2 is an interval and if so returns it. I know that you can work around it by having some fn start(&self) -> &T and fn end(&self) -> &T helper methods, but I hope you see that it was not very easy for me to know a priory which approach would be better. I guess I have wasted more time evaluating every possibility and actually finished coding neither