I'm writing an argument parser (don't worry, this won't become Yet Another Argument Parser on crates.io -- it's just for learning Rust).
The C++ library I'm trying to mimic (primarily interfacewise, not necessarily codewise) uses a 'Spec'-class which is used as a base class to derive switch/argument specific classes. The base class has two virtual functions which are meant to be overridden. One is called "proc_opt" and one is called "proc_arg". The "opt" version doesn't accept any arguments (i.e. is only used for command line switches without arguments) and the "arg" call includes an vector of strings which will contain the arguments to the option.
To illustrate:
class HelpSwitch : public Args::Spec
{
HelpSwitch() : Args::Spec('h', "help") {
}
void proc_opt() override {
// process -h, --help
}
}
class FileOpt : public Args::Spec
{
FileOpt() : Args::Spec('f', "file") {
nargs_ = 1;
}
void proc_arg(const std::vector<std::string>& args) override {
// process -f FILE, --file=FILE
}
}
So if a program is called using --help the first proc_opt will be called, and if a program is called using --file=foo.txt then proc_arg will be called and args[0] will be "foo.txt".
This clearly isn't isn't the whole story though. There's a driver object which is created from a "Args::Parser" class. The application creates a Parser object then adds these Spec-objects to the Parser object. One crucial part omitted from the prototypes above is that the parser also keeps track of an application context pointer which is passed to the handler functions. I.e:
MyContext myctx;
Args::Parser prsr(&myctx);
// .. add Spec objects to prsr ..
prsr.parse(argc, argv)
When the parser is run it'll pass the context pointer to the handlers, i.e. to modify the earlier examples:
// ...
void proc_opt(void *ptr) override {
MyContext *ctx = (MyContext*)ptr;
ctx->do_help = true;
}
// ...
void proc_arg(void *ptr, const std::vector<std::string>& args) override {
MyContext *ctx = (MyContext*)ptr;
ctx->fname = args[0]
}
// ...
I have two questions relating to this.
First, the Parser object will obviously use the Spec object's nargs to determine which callback to call. But how would one go about implementing this callback to Rust? I had a theory that I would use function pointers; they seem easy enough. But say I would like to pass a reference to the Spec object to the callback, then I end up with this conundrum:
pub struct Spec {
opt: Option<char>,
lopt: Option<String>,
name: Option<String>,
nargs: usize,
}
type SwitchHandler = fn(spec: &Spec);
type ArgHandler = fn(spec: &Spec, args: Vec<String>);
Now I have two handler "function pointer" types, but I need to place instances of them in the Spec object -- but I need Spec in order to create the function pointer type. "That's easy, I'll just forward declare Spec" I thought, but after a quick google I don't think there is forward declaration of structs in Rust?
Another way I thought about doing it was using traits; one for the proc_opt and one for proc_arg, but I'm not sure that's feasible or a good idea.
The second question relates to the application-specific context buffer. What would be an ideomatic way to pass an application-specific buffer from the application to the parser object and then on to the "proc_opt" and "proc_arg" functions? In C++ I just use a void pointer, and the application casts it to its own type in its handlers. On this one I'm completely stumped with regards to Rust. Again I was looking at using traits somehow, but traits are about methods and the context buffers are typically data-only, so it feels like going down the wrong path.