syn::ItemFn.to_token_stream hidden/guarded? - rust

I thought I'd try to write a basic function decorator that logs the input and output of functions as they are called. I have many functions that have roughly the same signature, and I'd like to log the io of common part. That is, think of adding a decorator like this
#[my_decorator]
foo(<unknown initial args>, final_arg: T) -> R {BODY}
would effectively turn foo into
foo(<unknown initial args>, final_arg: T) -> R {
let result = {BODY};
debug!("final_arg is {:?}" final_arg)
debug!("return value is {:?}" result)
result
}
I spent today searching around for best approaches to this and it appears that the best way would be to define a #[proc_macro_attribute]
I am at the following sanity-checking stub (which the documentation here would suggest is valid)
use proc_macro::TokenStream;
use syn;
use syn::ItemFn;
#[proc_macro_attribute]
pub fn log_io(_attr: TokenStream, item: TokenStream) -> TokenStream {
let item_fn: ItemFn = syn::parse_macro_input!(item);
item_fn.to_token_stream()
}
Which IIUC should be the No-Op decorator. But the compiler gives
error[E0599]: no method named `to_token_stream` found for struct `ItemFn` in the current scope
--> src/lib.rs:8:13
|
8 | item_fn.to_token_stream()
| ^^^^^^^^^^^^^^^ method not found in `ItemFn`
|
::: /Users/arthurtilley/.cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.21/src/to_tokens.rs:59:8
|
59 | fn to_token_stream(&self) -> TokenStream {
| --------------- the method is available for `ItemFn` here
|
= help: items from traits can only be used if the trait is in scope
help: one of the expressions' fields has a method of the same name
|
8 | item_fn.sig.to_token_stream()
| ++++
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
1 | use syn::__private::ToTokens;
|
Adding the suspicious looking use syn::__private::ToTokens; the compiler then gives
error[E0308]: mismatched types
--> src/lib.rs:9:5
|
7 | pub fn log_io(_attr: TokenStream, item: TokenStream) -> TokenStream {
| ----------- expected `proc_macro::TokenStream` because of return type
8 | let item_fn: ItemFn = syn::parse_macro_input!(item);
9 | item_fn.to_token_stream()
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `proc_macro::TokenStream`, found struct `TokenStream2`
None of this is mentioned in the documentation https://docs.rs/syn/latest/syn/struct.ItemFn.html so I'm a little stumped.
EDIT: #Shelvacu I'm using
syn = {version ="*" ("1.0.99"), features = ["full", "printing", "extra-traits"]}
proc-macro2 = "*" ("1.0.43")

If you click on TokenStream in fn to_token_stream you'll notice that it returns proc_macro2::TokenStream which is different from proc_macro::TokenStream that your function is defined to return. You can read more about this in the proc-macro2 docs. The way to convert proc_macro2::TokenStream to proc_macro::TokenStream as recommended in the docs is:
proc_macro::TokenStream::from(output)
In your case, this should work:
#[proc_macro_attribute]
pub fn log_io(_attr: TokenStream, item: TokenStream) -> TokenStream {
let item_fn: ItemFn = syn::parse_macro_input!(item);
proc_macro::TokenStream::from(item_fn.to_token_stream());
}
Also, you should import ToTokens trait from quote::ToTokens (from the quote crate) as the syn re-export is marked private.

Related

How to define impl on standard types in Rust?

How can we define a wrapper type around an existing type and define functions over it?
I have tried the below code and I am getting this error
struct Deploy(u8);
impl Deploy {
fn values_yaml(self) -> u8 {
self+1
}
fn chart(self) -> u8 {
self+1
}
}
fn main() {
let a = Deploy(1);
println!("Hello, world! {:?}", a.chart());
}
error:
error[E0369]: cannot add `{integer}` to `Deploy`
--> src/main.rs:5:11
|
5 | self+1
| ----^- {integer}
| |
| Deploy
|
note: an implementation of `Add<_>` might be missing for `Deploy`
Any suggestion is most welcome.
You're dealing with a tuple struct, with tuple size 1. The elements of tuples are accessed via the usual syntax for tuples in Rust:
self.0
Alternatively, you can also match the fields similarly to regular tuples.
let Deploy(resource_type) = self;

Constraint associated type of a generic associated type

I have a type Builder with a Generic Associated Type (GAT) InstanceForBuilder<'a>.
I'd like to write a function (build_with_42_for_bool<Builder>) that constraints the Builder to only those cases where Builder::InstanceForBuilder<'a>::InstanceProperty == bool (for all 'a).
I've been playing around for a while to get the syntax around this for <'a> right, but haven't been able to make this work.
The lifetime can't be a template argument of the function itself, because the reference only lives inside it.
Is this possible at all yet, given that GAT is an unstable feature?
playground
#![feature(generic_associated_types)]
// Trait definitions.
trait Builder {
type InstanceForBuilder<'a>: Instance<'a>;
fn build<'a>(&self, val: &'a usize) -> Self::InstanceForBuilder<'a>;
}
trait Instance<'a> {
// Some functions will only work when the instance has some concrete associated type.
type InstanceProperty;
}
fn build_with_42_for_bool<B: Builder>(builder: B)
where
// TODO: What do I put here to make this work?
for<'a> B::InstanceForBuilder<'a>: Instance<'a, InstanceProperty = bool>,
{
builder.build(&42);
// Do some testing here. The Instance won't be returned.
}
// Now try it out.
struct MyBuilder;
struct MyInstance<'a> {
val: &'a usize,
}
impl Builder for MyBuilder {
type InstanceForBuilder<'a> = MyInstance<'a>;
fn build<'a>(&self, val: &'a usize) -> Self::InstanceForBuilder<'a> {
MyInstance { val }
}
}
impl<'a> Instance<'a> for MyInstance<'a> {
type InstanceProperty = bool;
}
fn main() {
let builder = MyBuilder;
build_with_42_for_bool(builder); // TODO: Doesn't work
}
In my actual code, build_with_42_for_bool is a helper for testing that constructs the arguments passed to build in a specific way. For now I'll probably just inline that function everywhere, since the only problem is how to specify the lifetime of this one function. The code itself works fine.
Here's the full error:
Compiling pairwise-aligner v0.1.0 (/home/philae/git/eth/git/astar-pairwise-aligner)
error[E0271]: type mismatch resolving `for<'a> <<_ as Builder>::InstanceForBuilder<'a> as Instance<'a>>::InstanceProperty == bool`
--> examples/test.rs:45:5
|
45 | build_with_42(builder); // TODO: Doesn't work
| ^^^^^^^^^^^^^ expected `bool`, found associated type
|
= note: expected type `bool`
found associated type `<<_ as Builder>::InstanceForBuilder<'_> as Instance<'_>>::InstanceProperty`
= help: consider constraining the associated type `<<_ as Builder>::InstanceForBuilder<'_> as Instance<'_>>::InstanceProperty` to `bool`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
note: required by a bound in `build_with_42`
--> examples/test.rs:19:53
|
16 | fn build_with_42<B: Builder>(builder: B)
| ------------- required by a bound in this
...
19 | for<'a> B::InstanceForBuilder<'a>: Instance<'a, InstanceProperty = bool>,
| ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `build_with_42`
error[E0271]: type mismatch resolving `for<'a> <MyBuilder as Builder>::InstanceForBuilder<'a> == <MyBuilder as Builder>::InstanceForBuilder<'a>`
--> examples/test.rs:45:5
|
45 | build_with_42(builder); // TODO: Doesn't work
| ^^^^^^^^^^^^^ type mismatch resolving `for<'a> <MyBuilder as Builder>::InstanceForBuilder<'a> == <MyBuilder as Builder>::InstanceForBuilder<'a>`
|
note: expected this to be `<MyBuilder as Builder>::InstanceForBuilder<'a>`
--> examples/test.rs:32:35
|
32 | type InstanceForBuilder<'a> = MyInstance<'a>;
| ^^^^^^^^^^^^^^
= note: expected associated type `<MyBuilder as Builder>::InstanceForBuilder<'a>`
found struct `MyInstance<'a>`
help: a method is available that returns `<MyBuilder as Builder>::InstanceForBuilder<'a>`
--> examples/test.rs:8:5
|
8 | fn build<'a>(&self, val: &'a usize) -> Self::InstanceForBuilder<'a>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ consider calling `Builder::build`
note: required by a bound in `build_with_42`
--> examples/test.rs:19:40
|
16 | fn build_with_42<B: Builder>(builder: B)
| ------------- required by a bound in this
...
19 | for<'a> B::InstanceForBuilder<'a>: Instance<'a, InstanceProperty = bool>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `build_with_42`
For more information about this error, try `rustc --explain E0271`.
error: could not compile `pairwise-aligner` due to 2 previous errors
The correct syntax to do what you want is:
where B::InstanceForBuilder::InstanceProperty = bool
there's no need to introduce the for<'a> lifetime because the value of the associated type is not allowed to depend on the lifetime parameter. If you compile this code, you'll find that you get another error:
error: equality constraints are not yet supported in `where` clauses
--> src/main.rs:19:5
|
19 | B::InstanceForBuilder::InstanceProperty = bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not supported
|
= note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information
There's no immediate workaround for this. If you badly need this, you may be able to write a new trait, implement it only for bool, and then constrain things to that trait. That will get messy quickly though.
The answer from DreamConspiracy is a little outdated. I'd like to reuse the example codes from him to show a compiled solution
Instead of
where B::InstanceForBuilder::InstanceProperty = bool
You should write
fn build_with_42_for_bool_instance<'a, B, I>(builder: B)
where
B : Builder<InstanceForBuilder<'a>=I>,
I : Instance<'a, InstanceProperty=bool>,
{
builder.build(&42);
}

How can I create a generic type that includes a trait

I find I am using this pattern a lot.
Arc<Mutex<dyn SomeTrait + Send>>;
and so I thought I would do this:
pub type NicePtr<T> = Arc<Mutex<dyn T + Send>>;
but this does not compile
Compiling rsim v0.1.0 (C:\work\pdp\rsim)
error[E0404]: expected trait, found type parameter `T`
--> src\common.rs:9:37
|
9 | pub type NicePtr<T> = Arc<Mutex<dyn T + Send>>;
| ^ not a trait
I assume this is possible, but I just dont know the correct syntax.
An interesting note is that (it is the thing that you want)
use std::sync::{Arc, Mutex};
pub type NicePtr<T: Send> = Arc<Mutex<T>>;
does compile, but with a warning:
warning: bounds on generic parameters are not enforced in type aliases
--> src/lib.rs:2:21
|
2 | pub type NicePtr<T: Send> = Arc<Mutex<T>>;
| ^^^^
|
= note: `#[warn(type_alias_bounds)]` on by default
help: the bound will not be checked when the type alias is used, and should be removed
|
2 - pub type NicePtr<T: Send> = Arc<Mutex<T>>;
2 + pub type NicePtr<T> = Arc<Mutex<T>>;
|
As a comment points out, you can accomplish something similar to what you want with macros. You could have one macro that expands to a type and one that wraps Arc::new(Mutex::new()) (or any other pointer type that you decide is nice).
use std::sync::{Arc, Mutex};
use std::fmt::Display;
macro_rules! nice_ptr {
// for types
($t:ty) => {
Arc<Mutex<$t>>
};
// for traits
($t:expr) => {
Arc<Mutex<$t>>
}
}
macro_rules! nice_ptr_new{
($inner:expr) => {
Arc::new(Mutex::new($inner))
}
}
fn main() {
let example: nice_ptr!(dyn Display) = nice_ptr_new!("example");
println!("{}", example.lock().unwrap());
}
Using it with the wrong types even gives a helpful error message.
fn main() {
// arrays don't implement Display!
let example: nice_ptr!(dyn Display) = nice_ptr_new!([1,2,3]);
println!("{}", example.lock().unwrap());
}
15 | Arc::new(Mutex::new($inner))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `[{integer}; 3]` cannot be formatted with the default formatter
...
20 | let example: nice_ptr!(dyn Display) = nice_ptr_new!([1,2,3]);
| ---------------------- in this macro invocation
|
= help: the trait `std::fmt::Display` is not implemented for `[{integer}; 3]`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required for the cast to the object type `dyn std::fmt::Display`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

How do I pass a struct with type parameters as a function argument?

How do I pass an instance of EcsClient with the signature impl<P, D> EcsClient<P, D> where P: ProvideAwsCredentials, D: DispatchSignedRequest to a function as a reference in Rust? My attempt is thus:
extern crate rusoto;
use std::default::Default;
use rusoto::{ DefaultCredentialsProvider, Region };
use rusoto::ecs::{ EcsClient };
use rusoto::default_tls_client;
fn get_task_definition_revisions(client: &EcsClient) {
// Use EscClient instance here
}
fn main() {
let provider = DefaultCredentialsProvider::new().unwrap();
let client = EcsClient::new(default_tls_client().unwrap(), provider, Region::EuWest1).unwrap();
get_task_definition_revisions(&client);
}
This gives me the following error:
error[E0243]: wrong number of type arguments: expected 2, found 0
--> src/main.rs:9:43
|
9 | fn get_task_definition_revisions(client: &EcsClient) {
| ^^^^^^^^^ expected 2 type arguments
My attempted fix for this is such:
extern crate rusoto;
use std::default::Default;
use rusoto::{
DefaultCredentialsProvider,
Region,
ProvideAwsCredentials,
DispatchSignedRequest
};
use rusoto::ecs::{ EcsClient, ListTaskDefinitionsRequest };
use rusoto::default_tls_client;
fn get_task_definition_revisions(client: &EcsClient<ProvideAwsCredentials, DispatchSignedRequest>) {
// Use EcsClient instance here
}
fn main() {
let provider = DefaultCredentialsProvider::new().unwrap();
let client = EcsClient::new(default_tls_client().unwrap(), provider, Region::EuWest1);
get_task_definition_revisions(&client);
}
Which gives me:
error[E0277]: the trait bound `rusoto::ProvideAwsCredentials + 'static: std::marker::Sized` is not satisfied
--> src/main.rs:14:1
|
14 | fn get_task_definition_revisions(client: &EcsClient<P, D>) {
| _^ starting here...
15 | | let defs = client.list_task_definitions(&ListTaskDefinitionsRequest {
16 | | family_prefix: None,
17 | | max_results: None,
18 | | next_token: None,
19 | | sort: None,
20 | | status: Some("ACTIVE".to_string()),
21 | | });
22 | | }
| |_^ ...ending here: the trait `std::marker::Sized` is not implemented for `rusoto::ProvideAwsCredentials + 'static`
|
= note: `rusoto::ProvideAwsCredentials + 'static` does not have a constant size known at compile-time
= note: required by `rusoto::ecs::EcsClient`
error[E0277]: the trait bound `rusoto::DispatchSignedRequest + 'static: std::marker::Sized` is not satisfied
--> src/main.rs:14:1
|
14 | fn get_task_definition_revisions(client: &EcsClient<P, D>) {
| _^ starting here...
15 | | let defs = client.list_task_definitions(&ListTaskDefinitionsRequest {
16 | | family_prefix: None,
17 | | max_results: None,
18 | | next_token: None,
19 | | sort: None,
20 | | status: Some("ACTIVE".to_string()),
21 | | });
22 | | }
| |_^ ...ending here: the trait `std::marker::Sized` is not implemented for `rusoto::DispatchSignedRequest + 'static`
|
= note: `rusoto::DispatchSignedRequest + 'static` does not have a constant size known at compile-time
= note: required by `rusoto::ecs::EcsClient`
This feels like a rabbit hole I shouldn't be going down.
I've also tried changing the function signature to accept generics, however the EcsClient is a struct not a trait. Googling doesn't provide much help because I don't know the correct terms to search for.
This question seems to imply that I should be able to declare a function like fn my_func(client: &EcsClient) { ... } and it will work, so why doesn't the above example?
The problem is that EcsClient is not a type, it's a blueprint to build a type (also known as "type constructor").
As a result, you cannot use just EcsClient when a type is required, be it in functions or for struct members; instead, each time, you must use it to build a type by specifying its generic parameters.
Thus, the first step is to introduce the type parameters:
fn get_task_definition_revisions<P, D>(client: &EcsClient<P, D>) {}
Now, however, the compiler will complain that the P and D are insufficiently constrained: EcsClient only accept a very specific kind of P and D!
The next step, thus, is to look-up the bounds that are specified for P and D in the definition of EcsClient and apply them. It's just copy/paste at this point:
fn get_task_definition_revisions<P, D>(client: &EcsClient<P, D>)
where P: ProvideAwsCredentials,
D: DispatchSignedRequest
{
}
And then you're golden.
If you need more capabilities of P or D for this specific function, feel free to constrain them adequately by adding more bounds using +:
fn get_task_definition_revisions<P, D>(client: &EcsClient<P, D>)
where P: ProvideAwsCredentials + 'static,
D: DispatchSignedRequest
{
}
If you wonder why Rust chose to have you repeat the bounds for P and D when it could perfectly infer them, it's because it cares about you. More specifically, it cares about you 6 months from now, and the next maintainer to come. So, taking the stance that you write once and read many, it forces you to copy the bounds so that later you don't have to wonder what they are, and drill down recursively in each type/function used to painfully aggregate all the pieces. In Rust, the next time you read the function, you'll have all the information right there.
I was understanding the syntax incorrectly. It seems that P and D are placeholders, like T would be. I need to specify what those types are, so the signature now looks like this:
fn get_task_definition_revisions<P: ProvideAwsCredentials, D: DispatchSignedRequest>(client: &EcsClient<P, D>) {
...
}
I don't use P or D in the function body, but they must be declared.
The full example now looks like:
extern crate rusoto;
use std::default::Default;
use rusoto::{
DefaultCredentialsProvider,
Region,
ProvideAwsCredentials,
DispatchSignedRequest
};
use rusoto::ecs::{ StringList, EcsClient, ListTaskDefinitionsRequest };
use rusoto::default_tls_client;
fn get_task_definition_revisions<P: ProvideAwsCredentials, D: DispatchSignedRequest>(client: &EcsClient<P, D>) {
let res = client.list_task_definitions(&ListTaskDefinitionsRequest {
family_prefix: None,
max_results: None,
next_token: None,
sort: None,
status: Some("ACTIVE".to_string()),
});
// ...
}
fn main() {
let provider = DefaultCredentialsProvider::new().unwrap();
let client = EcsClient::new(default_tls_client().unwrap(), provider, Region::EuWest1);
get_task_definition_revisions(&client);
}
I'm still not entirely sure why this works or is required but I hope this answer helps someone else.

How to check whether a path exists?

The choice seems to be between std::fs::PathExt and std::fs::metadata, but the latter is suggested for the time being as it is more stable. Below is the code I have been working with as it is based off the docs:
use std::fs;
pub fn path_exists(path: &str) -> bool {
let metadata = try!(fs::metadata(path));
assert!(metadata.is_file());
}
However, for some odd reason let metadata = try!(fs::metadata(path)) still requires the function to return a Result<T,E> even though I simply want to return a boolean as shown from assert!(metadata.is_file()).
Even though there will probably be a lot of changes to this soon enough, how would I bypass the try!() issue?
Below is the relevant compiler error:
error[E0308]: mismatched types
--> src/main.rs:4:20
|
4 | let metadata = try!(fs::metadata(path));
| ^^^^^^^^^^^^^^^^^^^^^^^^ expected bool, found enum `std::result::Result`
|
= note: expected type `bool`
found type `std::result::Result<_, _>`
= note: this error originates in a macro outside of the current crate
error[E0308]: mismatched types
--> src/main.rs:3:40
|
3 | pub fn path_exists(path: &str) -> bool {
| ________________________________________^
4 | | let metadata = try!(fs::metadata(path));
5 | | assert!(metadata.is_file());
6 | | }
| |_^ expected (), found bool
|
= note: expected type `()`
found type `bool`
Note that many times you want to do something with the file, like read it. In those cases, it makes more sense to just try to open it and deal with the Result. This eliminates a race condition between "check to see if file exists" and "open file if it exists". If all you really care about is if it exists...
Rust 1.5+
Path::exists... exists:
use std::path::Path;
fn main() {
println!("{}", Path::new("/etc/hosts").exists());
}
As mental points out, Path::exists simply calls fs::metadata for you:
pub fn exists(&self) -> bool {
fs::metadata(self).is_ok()
}
Rust 1.0+
You can check if the fs::metadata method succeeds:
use std::fs;
pub fn path_exists(path: &str) -> bool {
fs::metadata(path).is_ok()
}
fn main() {
println!("{}", path_exists("/etc/hosts"));
}
You can use std::path::Path::is_file:
use std::path::Path;
fn main() {
let b = Path::new("file.txt").is_file();
println!("{}", b);
}
https://doc.rust-lang.org/std/path/struct.Path.html#method.is_file

Resources