As rust does not have function overloading, how would you wrap an API endpoint with optional parameters?
Lets say I have an endpoint that takes a file and optional parameter like:
url: /folder/upload
file: file to upload
target: target folder. Leave empty to upload to your home folder.
As far as I can tell there are three possible solutions to this:
Empty target equals no target. Just pass "" as the target.
I usually prefer making it generic over an Into<Option<T>>, that way it's easy to just pass None for an unused parameter and you don't have to pass an explicit Some when it is used.
If there are many parameters, consider wrapping them in a struct of optional fields that implements Default.
One thing to be careful with, if you go down this route - it can lead to situations where type inference fails when you pass None. gtk-rsremoved this pattern from their codebase for this reason.
I like it. While it is still not as clean as just leaving out unneeded parameters it gets rid of the ugly Some.
Did not even think about Into. I am still just at the beginning of my rust journey
Using only std traits, it seems we cannot support all of Some("foo"), Some("foo".to_owned()), "foo", "foo".to_owned(), and None.
If you want to accept all of them, you might have to write your own trait...
impl Into<Option<String>> argument: &str family is NG
pub
fn folder_upload<'target> (file: File, target: Option<&'target str>)
-> Result<Response, Error>
{
/* body using `file: File` and `target: Option<&'target str>` */
}
you can do:
#[inline]
pub
fn folder_upload<'target> (file: File) // only the mandatory args
-> FolderUploadBuilder<'target>
{
FolderUploadBuilder {
file,
target: None,
}
}
#[must_use = "This function is lazy: you need to chain with `.call()`"]
pub
struct FolderUploadBuilder<'target> {
file: File,
target: Option<&'target str>, // Optional arg
}
impl<'target> FolderUploadBuilder<'target> {
pub
fn call (self)
-> Result<Response, Error> // Initial return value of our function
{
let Self { file, target } = self;
/* body using `file: File` and `target: Option<&'target str>` */
}
#[inline]
pub
fn with_target (self, target: &'target str)
-> Self
{
Self {
target: Some(target),
.. self
}
}
}
So that using this goes as follows:
with a specific target
let file = ...;
let target = ...;
let res = folder_upload(file).with(target).call();
// or
let res =
folder_upload(file)
.with(target) // This line can easily be commented in/out
.call()
;
no specific target
let file = ...;
let res = folder_upload(file).call();
Thank you everyone. The builder pattern seems really good if I have a lot of optional parameters, so I can avoid the my_function(argument: &str, None, None, None, None) case. I will keep it in mind for the future.
I have not really looked at macros yet, as they seem difficult for a beginner.
Because the maximum number of optional arguments in my case is 2, I will go with the Into<Option<&str>> this time.