How to use variable in syn parse_quote - rust

I'm having issues finding how to use variables in parse_quote in the documentation.
I have a string variable that I need to insert into a turbofish statement I create with parse_quote.
Below are my attempts after reading some documentation and this question.
let my_var = String::from("hello");
let stmt = syn::parse_quote! { handle.add_class<#(my_var)::#(my_var)>(); };
This fails to compile with a warning about expecting other keywords.
I've also tried the following based on the documentation for parse_quote.
let my_var = quote!("hello");
let stmt = syn::parse_quote! { handle.add_class<#(my_var)::#(my_var)>(); };
This throws a similar error to the one above.

Related

Trying to get rid of unwraps

I have this line in my program:
let date = file.metadata().unwrap().modified().unwrap();
Can it be changed into form of if let Ok(date) = file.metadata().something.... and still be one liner?
Forgot to add: can't use ? operator, bc this is in a closure in for_each().
Using Result::and_then:
if let Ok(date) = file.metadata().and_then(|md| md.modified()) {
// stuff
}
Using the "try" operator (?):
// containing function returns `Result<T, E>` where `E: From<io::Error>`
let date = file.metadata()?.modified()?;
If you're inside a closure which must return (), and you want to ignore the error, I'd actually recommend using let else as such:
let Ok(metadata) = file.metadata() else { return };
let Ok(date) = metadata.modified() else { return };
// ...
This has the advantage that it doesn't increase the indentation level.

Rust lifetimes in if statement

I have an if statement in a for loop, and I want it to create a variable with the lifetime of that iteration of the for loop.
for condition_raw in conditions_arr {
println!("{}", condition_raw);
let matching = !condition_raw.contains('!');
if matching {
let index = condition_raw.find('=').unwrap_or_default();
} else {
let index = condition_raw.find('!').unwrap_or_default();
}
let key = &condition_raw[..index];
}
let key = &condition_raw[..index]; currently throws cannot find value index in this scope
not found in this scope rustc E0425
I'll ignore the condition variable which does not seem to be used at all in your example.
A let statement creates a binding that holds at most for the current scope. For this reason, when you create the index variable inside the if, you are not making it accessible anywhere else. There are two ways to solve this issue.
The first way is to explicitly declare index as being part of the outer scope, and only define it inside the if statement.
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index;
if matching {
index = condition_raw.find('=').unwrap_or_default();
} else {
index = condition_raw.find('!').unwrap_or_default();
}
let key = &condition_arr[..index];
}
There is no risk of accidentally not defining index, since Rust will make sure that index is defined (exactly once) in all possible branching of your code before it is used. Yet, it's not a pretty solution because it violates a "locality" principle, that is that pieces of code should have effects on or pieces of code that are sufficiently close. In this case, the let index; is not too far from its definition, but it could be arbitrarily far, which makes it painful for someone who reads your code to remember that there is a declared but not yet defined.
Alternatively, you could use the fact that most things in Rust are expressions:
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index = if matching {
condition_raw.find('=').unwrap_or_default();
} else {
condition_raw.find('!').unwrap_or_default();
}
let key = &condition_arr[..index];
}
But, in fact, you could factorize your code even more, which is usually better:
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index = condition_raw.find(if matching {
'='
} else {
'!'
}).unwrap_or_default();
let key = &condition_arr[..index];
Or, even more
for condition_raw in conditions_arr {
let index = condition_raw
.find('!')
.or_else(|| condition_raw.find('='))
.unwrap_or_default();
let key = &condition_arr[..index];
}
An idiomatic way to assign variables from an if else statement is as follows:
let index: usize = if matching {
condition_raw.find('=').unwrap_or_default()
} else {
condition_raw.find('!').unwrap_or_default()
};
Idiomatic way of assigning a value from an if else condition in Rust
In Rust, an if/else block is an expression. That is to say, the block itself has a value, equivalent to the last expression in whatever section was executed. With that in mind, I would structure your code like this:

Change relative location for PathBuf

I have a few PathBufs in my Rust application:
let mut dog_path = PathBuf::from("./animals/dog.png");
let mut cow_path = PathBuf::from("./animals/bovine/cow.jpg");
How could I change these PathBufs so that they're being referred to from the ./animals directory?
// an operation on dog_path
// same operation on cow_path
assert_eq!(PathBuf::from("./dog.png"), dog_path);
assert_eq!(PathBuf::from("./bovine/cow.jpg"), cow_path);
I think you want Path::strip_prefix:
let dog_path = PathBuf::from("./animals/dog.png");
let cow_path = PathBuf::from("./animals/bovine/cow.jpg");
let dog_path_rel = dog_path.strip_prefix("./animals").unwrap();
let cow_path_rel = cow_path.strip_prefix("./animals").unwrap();
assert_eq!(Path::new("dog.png"), dog_path_rel);
assert_eq!(Path::new("bovine/cow.jpg"), cow_path_rel);
But that won't include the leading ./. If that's important to you, you can add it manually:
let dog_path_prefixed = Path::new("./").join(dog_path_rel);
let cow_path_prefixed = Path::new("./").join(cow_path_rel);
assert_eq!(PathBuf::from("./dog.png"), dog_path_prefixed);
assert_eq!(PathBuf::from("./bovine/cow.jpg"), cow_path_prefixed);
playground
Note that strip_prefix returns a Result, meaning it could fail if the path doesn't begin with the given prefix. You may want to handle this case instead of unwraping the result (causing your program to exit with a panic), or you may want to use .expect("your message here") instead to provide a meaningful error message.
If you want a general solution you could look at relative-path crate. It looks like it provide the functionality you want.
use std::path::PathBuf;
use relative_path::RelativePath;
fn main() {
let dog_path = PathBuf::from("./animals/dog.png");
let cow_path = PathBuf::from("./animals/bovine/cow.jpg");
let dog_path = RelativePath::from_path(&dog_path).unwrap();
let cow_path = RelativePath::from_path(&cow_path).unwrap();
let animals_dir = RelativePath::new("./animals");
let dog_path = animals_dir.relative(&dog_path).to_path(".");
let cow_path = animals_dir.relative(&cow_path).to_path(".");
assert_eq!(PathBuf::from("./dog.png"), dog_path);
assert_eq!(PathBuf::from("./bovine/cow.jpg"), cow_path);
}
This is a quick draft, but it shows how to do in a generic way what you are trying to accomplish. I think it could be further optimized, but I literally found this crate 10 minutes ago.

error handling when unwrapping several try_into calls

I have a case where I need to parse some different values out from a vector.
I made a function for it, that returns a option, which either should give a option or a None, depending on whether the unwrapping succeeds.
Currently it looks like this:
fn extract_edhoc_message(msg : Vec<u8>)-> Option<EdhocMessage>{
let mtype = msg[0];
let fcnt = msg[1..3].try_into().unwrap();
let devaddr = msg[3..7].try_into().unwrap();
let msg = msg[7..].try_into().unwrap();
Some(EdhocMessage {
m_type: mtype,
fcntup: fcnt,
devaddr: devaddr,
edhoc_msg: msg,
})
}
But, I would like to be able to return a None, if any of the unwrap calls fail.
I can do that by pattern matching on each of them, and then explicitly return a None, if anything fails, but that would a lot of repeated code.
Is there any way to say something like:
"if any of these unwraps fail, return a None?"
This is exactly what ? does. It's even shorter than the .unwrap() version:
fn extract_error_message(msg: Vec<u8>) -> Option<EdhocMessage> {
let m_type = msg[0];
let fcntup = msg[1..3].try_into().ok()?;
let devaddr = msg[3..7].try_into().ok()?;
let edhoc_msg = msg[7..].try_into().ok()?;
Some(EdhocMessage {
m_type,
fcntup,
devaddr,
edhoc_msg
})
}
See this relevant part of the Rust Book.

Is there a way to a prevent a String from being interpolated in Swift?

I'm experimenting around the idea of a simple logger that would look like this:
log(constant: String, _ variable: [String: AnyObject]? = nil)
Which would be used like this:
log("Something happened", ["error": error])
However I want to prevent misuse of the constant/variable pattern like the following:
log("Something happened: \(error)") // `error` should be passed in the `variable` argument
Is there a way to make sure that constant wasn't constructed with a string interpolation?
You could use StaticString instead of String:
func log(constant: StaticString, _ variable: [String: AnyObject]? = nil) {
// You can retrieve `String` from `StaticString`
let msg = constant.stringValue
}
let foo = 1
log("test \(foo)") // -> error: cannot invoke 'log' with an argument list of type '(String)'

Resources