In the following code, returning the None value causes the compiler to throw an error regarding an Option being used while it expected an &str. I have changed it to return an empty string via a match-block, but I require the target variable to be completely omitted if the string is empty rather than containing "-h".
How can a null value of sorts be used in this situation?
// match-block
let cert_type = match host_flag {
Some(_) => "-h",
None => "",
};
// Target variable to ignore if value is empty string rather than `"-h"`
&cert_type
The following code is a verbatim chunk of the actual code:
// Check whether a host certificate has been requested rather than a user certificate.
// If so, add the host-certificate option to the OpenSSH command. If not, do not add the host-certificate option
// to the OpenSSH command.
let cert_type = match host_flag {
Some(_) => "-h",
None => "",
};
// Begin generating the OpenSSH certificates.
println!("Generating OpenSSH certificate for {}...", &pub_key_principal);
let cert_gen = Command::new("ssh-keygen")
.args([
// OpenSSH certificate-authority private-key.
"-s", &config.key.ca_priv_key,
// OpenSSH public-key principal.
"-I", pub_key_principal,
// Add host-certificate option if a host certificate has been requested.
&cert_type,
// OpenSSH remote principal(s).
"-n", &remote_principal,
// OpenSSH-certificate validity timeframe.
"-V", &target_validity,
// Clear default OpenSSH-certificate permissions.
"-O", "clear",
// Add pty permission to OpenSSH certificate.
"-O", "permit-pty",
// Inject the contents of the certificate-serial-file as the certificate serial number.
"-z", &serial_int.to_string(),
// OpenSSH public-key file to sign.
&pub_key.display().to_string(),
])
.status()
.unwrap_or_else(|_| {
eprintln!("\x1b[1;31mERROR (8):\x1b[0m Failed to generate OpenSSH certificate for {}!",
&pub_key.display().to_string());
process::exit(8);
});
I attempted to use unwrap() and its variants, to convert the Option to an &str, but the program panics when host_flag is omitted (via clap on CLI), hence my usage of an empty string, instead.
Ironically this is a great example of why NULL is often called the billion dollar mistake and why Rust does not have this concept.
What would happen, if actually cert_typewasNULL?
Then you'd basically create a null pointer at &cert_type and pass it to args() which would lead us deep into UB territory.
It is quite possible that I worded it incorrectly. I couldn't think of a better word for it. As stated in my initial post, the point is to remove the variable, entirely, when the result is an empty string/None. Whether the wording is correct or incorrect is a different matter.
rust's Command::args() is not like a shell, where a variable is completely removed when its expansion (in an unquoted word) is empty, because shell parse the entire command line as whole.
in rust, the args are separate elements, you'll need to manually splice the optional argument into the sequence of arguments.
luckily, in this example, here's a neat trick using Iterator::chain():
let cert_type = host_flag.map(|_| "-h");
let cert_gen = Command::new("ssh-keygen")
.args(std::iter::chain(
cert_type,
[
// OpenSSH certificate-authority private-key.
"-s", &config.key.ca_priv_key,
// OpenSSH public-key principal.
"-I", pub_key_principal,
// OpenSSH remote principal(s).
"-n", &remote_principal,
// OpenSSH-certificate validity timeframe.
"-V", &target_validity,
// Clear default OpenSSH-certificate permissions.
"-O", "clear",
// Add pty permission to OpenSSH certificate.
"-O", "permit-pty",
// Inject the contents of the certificate-serial-file as the certificate serial number.
"-z", &serial_int.to_string(),
// OpenSSH public-key file to sign.
&pub_key.display().to_string(),
]
))
.status()
//...
because Command::args() accept an Iterator, and both array and Option implement Iterator, you can chain them together.
the above snippet changed the order, to preserve the order, something like this should work:
Command::new("ssh-keygen")
.args(
// the `chain` method is defined on `Iterator`, not `IntoIterator`
// need an `Iterator` type to kick start the chain
// this example I juse use an empty iterator
std::iter::empty().chain([
// OpenSSH certificate-authority private-key.
"-s", &config.key.ca_priv_key,
// OpenSSH public-key principal.
"-I", pub_key_principal,
]).chain(
// Add host-certificate option if a host certificate has been requested.
cert_type,
).chain([
// OpenSSH remote principal(s).
"-n", &remote_principal,
// OpenSSH-certificate validity timeframe.
"-V", &target_validity,
// Clear default OpenSSH-certificate permissions.
"-O", "clear",
// Add pty permission to OpenSSH certificate.
"-O", "permit-pty",
// Inject the contents of the certificate-serial-file as the certificate serial number.
"-z", &serial_int.to_string(),
// OpenSSH public-key file to sign.
&pub_key.display().to_string(),
])
)
//...
I was able to compile and run the program after implementing the marked solution. The program no longer panics when the option is not passed via CLI, and works as intended when it is.
Thank-you to everyone for the explanations and example code.
I was actually about to try this multiple-args() approach until you showed me the Iterator approach. It's going to be much cleaner to use this approach, instead.
I will keep the current solution for consistency, and because it does work, even if not as clean as the updated solution.
technically rust has the concept of null when using raw pointers,
its just that safe rust never uses raw pointers, or any other nullable type, using options instead.