Open Boscop opened 3 years ago
Interesting idea. It sounds like what we really want is one of two things:
Into<OsString>
IntoIterator<Item = impl Into<OsString>>
(taking advantage of the fact that that Option
is also an iterator)Is it possible to implement something like CmdArg
to abstract over this in stable Rust? Do we need to wait for specialization to land?
@oconnor663 It seems it's not possible to due orphan rules to impl a trait for both cases, like:
trait CmdArg {}
impl<T: Into<OsString>> CmdArg for T {}
impl<T: Into<OsString>, I: IntoIterator<Item = T>> CmdArg for I {}
error[E0119]: conflicting implementations of trait CmdArg
So I suggest requiring the user to specify for each arg which case it is, with a small syntax addition, e.g. args that are iterators are prepended with ..
, like:
cmd!("prog", "-foo", ..cond.then_some(&["-bar", "-baz"]))
Currently the
cmd!()
macro only works for a fixed number of args. In real-world scenarios, often some args have to be passed only conditionally, and then thecmd!()
macro can't be used. Leading to hard to read code where alet mut args: Vec<String>
is constructed and conditionally pushed to. And then it makes the pushing of static arg strs unnecessarily verbose:args.push("-foo".to_string());
This becomes even less ergonomic when dealing with paths such that the whole Vec has to now beOsString
.The ergonomics of passing some args conditionally to
cmd!()
could be greatly improved by allowingcmd!()
to takeOption<T>
whereT: Into<OsString>
! So that one can write:cmd!("prog", "-foo", cond.then_some("-bar"), "-baz")
After adding this, it also makes sense to add multiple args whose existence depends on the same condition. This requires allowing passing something like
T: Iterator<Item = U>
whereU: Into<OsString>
or simplyVec<U>
/&[U]
. So that one can write:cmd!("prog", "-foo", cond.then_some(&["-bar", "-baz"]))
With these small changes, the ergonomics of
duct
would be greatly improved :)Notes on implementation: The bound of the
cmd
function would be changed toU::Item: Into<Option<OsString>>
, and in it's body, it would then be:But: We don't want to require all unconditional args to the
cmd!()
to be wrapped inSome
. Although in the body of the macro, we can only do one.into()
call, since we don't know whether the arg type isT: Option<OsString>
orT: OsString
. So solve this problem, it could help to introduce a new traitCmdArg
(which is essentially acting likeInto<Option<OsString>>
but allowing a conversion fromT: Into<OsString>
toOption<OsString>
in one function call (as well as fromOption<OsString>
) and then changing the trait bound of thecmd
function to takeU::Item: CmdArg
.