Rust Book 12.3 - unresolved import error [E4032] - rust

I am new to Rust and working on the exercise in Chapter 12.3 in the book.
I am pretty confident that my code is the same as that in the book (hard to tell for sure because of the 'snips'). However I get an unresolved import error when I try cargo build or cargo run from the project directory, minigrep/
src/main.rs
use std::env;
use std::process;
use minigrep;
use minigrep::Config;
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
println!("Problem parsing args: {}", err);
process::exit(1);
});
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
if let Err(e) = minigrep::run(config) {
println!("Application error: {}", e);
process::exit(1);
}
}
src/lib.rs
use std::fs;
use std::error::Error;
pub struct Config {
pub query: String,
pub filename: String,
}
impl Config {
pub fn new(args: &[String]) -> Result <Config, &'static str> {
if args.len() < 3 {
return Err("not enough args");
}
let query = args[1].clone();
let filename = args[2].clone();
Ok(Config { query, filename })
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.filename)?;
println!("With text:\n {}", contents);
Ok(())
}

There is a contradiction between the code samples for the exercise in chapter 12.3 and the earlier section in chapter 7: '7.2 -- Modules and use to control scope -- Separating modules into different files'.
Using 7.2's syntax complies:
mod lib; //.. instead of 'use minigrep'
use lib::Config; //.. instead of 'use minigrep::Config'

Neither of the above answers is correct. Using cargo clean is the correct solution. The incremental build process that produces that red underline in the IDE is a result of cached information about the program and library.
$ cargo clean

This seems to be a VSCode issue. Simply restarting VSCode makes the error go away, as discussed on this GitHub issue: https://github.com/rust-lang/vscode-rust/issues/686

I had this issue because I accidentally chose the wrong file name for the lib.rs. Make sure the file is named lib.rs without a typo. Other file names do not work here because of the use statement.
When you write use minigrep::Config, the compiler will look for a lib.rs file in the current project minigrep to resolve the imports.

I just ran across the same problem when reviewing The Rust Programming Language. The way I got it to compile was to remove the line use minigrep; from src/main.rs and add extern crate minigrep to the top of the file. The other file can stay as it is. Not sure if this a typo in the book (if it is then it is in multiple versions) or if there is a change to the behavior of the Rust compiler to accommodate the new syntax and we are just using a version that is too old. For comparison my version is 1.31.0.

Related

Cargo: How to include the entire directory or file in feature flags?

I'm working on a Rust project. I'm using Cargo feature flags for conditional compilation of the some code. There are cases where I have to include the entire file in the feature flags so doing so adding #[cfg(feature="my-flag")] over every function & use statement doesn't make much sense.
So to include the entire file in the feature flag I'm thinking to surround all the contents in the file a block & add the feature flag for the block.
#[cfg(feature="my-flag")]
{
use crate::access_control::{func1, func2};
use crate::models;
...
#[derive(Debug)]
pub enum MyEnum{..}
#[derive(Clone)]
pub Struct MyStruct{..}
pub fn my_func() {...}
fn my_func_internal() {...}
...
}
But I'm getting the error Syntax Error: expected an item after attributes
Also, there are also some cases where I want the entire directory to be included the feature flags. How should I go about it? Doing the adding feature flags for every file is one way. Does a better way exist?
As in #MarcusDunn's answer, the proper solution is to apply the attribute to the mod declaration:
// This conditionally includes a module which implements WEBP support.
#[cfg(feature = "webp")]
pub mod webp;
However for the sake of completeness, I would point out that attributes can be applied to the item they're in instead of being applied to the following item. These are called "inner attributes" and are specified by adding a ! after the #:
#![cfg(feature="my-flag")] // Applies to the whole file
use crate::access_control::{func1, func2};
use crate::models;
#[derive(Debug)]
pub enum MyEnum {}
#[derive(Clone)]
pub struct MyStruct {}
pub fn my_func() {}
fn my_func_internal() {}
From https://doc.rust-lang.org/cargo/reference/features.html
// This conditionally includes a module which implements WEBP support.
#[cfg(feature = "webp")]
pub mod webp;
This could be an entire directory - or a single file, depends how you structure your modules.
You can use conditional compilation on modules. Perhaps something like this would work for your use case:
#[cfg(feature = "feat")]
use feat::S;
#[cfg(not(feature = "feat"))]
use no_feat::S;
mod feat {
pub const S: &str = "feat";
}
mod no_feat {
pub const S: &str = "no_feat";
}
fn main() {
println!("{}", S);
}
Running with cargo run:
no_feat
Running with cargo run --features feat:
feat
You can use the cfg-if crate:
cfg_if::cfg_if! {
if #[cfg(feature = "my-flag")] {
// ...
}
}

How to pretty print Syn AST?

I'm trying to use syn to create an AST from a Rust file and then using quote to write it to another. However, when I write it, it puts extra spaces between everything.
Note that the example below is just to demonstrate the minimum reproducible problem I'm having. I realize that if I just wanted to copy the code over I could copy the file but it doesn't fit my case and I need to use an AST.
pub fn build_file() {
let current_dir = std::env::current_dir().expect("Unable to get current directory");
let rust_file = std::fs::read_to_string(current_dir.join("src").join("lib.rs")).expect("Unable to read rust file");
let ast = syn::parse_file(&rust_file).expect("Unable to create AST from rust file");
match std::fs::write("src/utils.rs", quote::quote!(#ast).to_string());
}
The file that it creates an AST of is this:
#[macro_use]
extern crate foo;
mod test;
fn init(handle: foo::InitHandle) {
handle.add_class::<Test::test>();
}
What it outputs is this:
# [macro_use] extern crate foo ; mod test ; fn init (handle : foo :: InitHandle) { handle . add_class :: < Test :: test > () ; }
I've even tried running it through rustfmt after writing it to the file like so:
utils::write_file("src/utils.rs", quote::quote!(#ast).to_string());
match std::process::Command::new("cargo").arg("fmt").output() {
Ok(_v) => (),
Err(e) => std::process::exit(1),
}
But it doesn't seem to make any difference.
The quote crate is not really concerned with pretty printing the generated code. You can run it through rustfmt, you just have to execute rustfmt src/utils.rs or cargo fmt -- src/utils.rs.
use std::fs;
use std::io;
use std::path::Path;
use std::process::Command;
fn write_and_fmt<P: AsRef<Path>, S: ToString>(path: P, code: S) -> io::Result<()> {
fs::write(&path, code.to_string())?;
Command::new("rustfmt")
.arg(path.as_ref())
.spawn()?
.wait()?;
Ok(())
}
Now you can just execute:
write_and_fmt("src/utils.rs", quote::quote!(#ast)).expect("unable to save or format");
See also "Any interest in a pretty-printing crate for Syn?" on the Rust forum.
As Martin mentioned in his answer, prettyplease can be used to format code fragments, which can be quite useful when testing proc macro where the standard to_string() on proc_macro2::TokenStream is rather hard to read.
Here a code sample to pretty print a proc_macro2::TokenStream parsable as a syn::Item:
fn pretty_print_item(item: proc_macro2::TokenStream) -> String {
let item = syn::parse2(item).unwrap();
let file = syn::File {
attrs: vec![],
items: vec![item],
shebang: None,
};
prettyplease::unparse(&file)
}
I used this in my tests to help me understand where is the wrong generated code:
assert_eq!(
expected.to_string(),
generate_event().to_string(),
"\n\nActual:\n {}",
pretty_print_item(generate_event())
);
Please see the new prettyplease crate. Advantages:
It can be used directly as a library.
It can handle code fragments while rustfmt only handles full files.
It is fast because it uses a simpler algorithm.
Similar to other answers, I also use prettyplease.
I use this little trick to pretty-print a proc_macro2::TokenStream (e.g. what you get from calling quote::quote!):
fn pretty_print(ts: &proc_macro2::TokenStream) -> String {
let file = syn::parse_file(&ts.to_string()).unwrap();
prettyplease::unparse(&file)
}
Basically, I convert the token stream to an unformatted String, then parse that String into a syn::File, and then pass that to prettyplease package.
Usage:
#[test]
fn it_works() {
let tokens = quote::quote! {
struct Foo {
bar: String,
baz: u64,
}
};
let formatted = pretty_print(&tokens);
let expected = "struct Foo {\n bar: String,\n baz: u64,\n}\n";
assert_eq!(formatted, expected);
}

Is there a way to get the file and the module path of where a procedural macro is attached at compile-time?

I'm looking for the equivalent of file!() & module_path!() in a procedural macro context.
For example, the following doesn't work:
file.rs:
#[some_attribute]
const A: bool = true;
macro.rs:
#[proc_macro_attribute]
pub fn some_attribute(attr: TokenStream, input: TokenStream) -> TokenStream {
println!("{}", file!());
input
}
This prints macro.rs which makes sense, but what I want is file.rs. Is there a way to achieve this? Is there also a similar way for module_path!()?
A requirement of this is that has to happen at compile-time.
I'm trying to create a file in the OUT_DIR containing constant values where the attribute is added with the module and the file that they are in.
I had the same problem and found out that Rust added a new experimential API to Rust macros (#54725) which allows exaclty what you want:
#![feature(proc_macro_span)]
#[proc_macro]
pub(crate) fn do_something(item: TokenStream) -> TokenStream {
let span = Span::call_site();
let source = span.source_file();
format!("println!(r#\"Path: {}\"#)", source.path().to_str().unwrap())
.parse()
.unwrap()
}
use my_macro_crate::*;
fn main() {
println!("Hello, world!");
do_something!();
}
Will output:
Hello, world!
Path: src\main.rs
Important
Apart from this API being experimential, the path might not be a real OS path. This can be the case if the Span was generated by a macro. Visit the documentation here.
The problem here is that println!("{}", file!()); is executed at compile time and not at runtime. Similar to an answer that was recently given here, you can edit the original function and insert your code at the beginning of it, which will be executed at runtime this time. You can still use the procedural macros file!() and module_path!(). Here is a macro.rs with this approach:
#[proc_macro_attribute]
pub fn some_attribute(_attr: TokenStream, input: TokenStream) -> TokenStream {
// prefix to be added to the function's body
let mut prefix: TokenStream = "
println!(\"Called from {:?} inside module path {:?}\",
file!(), module_path!());
".parse().unwrap();
// edit TokenStream
input.into_iter().map(|tt| {
match tt {
TokenTree::Group(ref g) // match function body
if g.delimiter() == proc_macro::Delimiter::Brace => {
// add logic before function body
prefix.extend(g.stream());
// return new function body as TokenTree
TokenTree::Group(proc_macro::Group::new(
proc_macro::Delimiter::Brace, prefix.clone()))
},
other => other, // else just forward
}
}).collect()
}
You can use it like this in your main.rs:
use mylib::some_attribute;
#[some_attribute]
fn yo() -> () { println!("yo"); }
fn main() { yo(); }
Note that the code is added before what's inside of the function's body. We could have inserted it at the end, but this would break the possibility of returning a value without a semicolon.
EDIT: Later realized that the OP wants it to run at compile time.

Rust not creating function in lib extern FFI

I have many Rust functions working perfectly across Ruby FFI. But following directions from two different sites for creating a free_array method is not making the method available in the linked library.
This example is the working example of freeing a String returned from Ruby.
use libc::c_char;
use std::ffi::CString;
#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
unsafe {
if s.is_null() { return }
CString::from_raw(s)
};
}
And here are two attempts at implementing a way to free the memory of an Array.
use std::mem::transmute;
use ruby_array::RubyArray;
#[no_mangle]
pub extern "C" fn free_array(ra: *mut RubyArray) {
let _ra: Box<RubyArray> = unsafe{ transmute(ra) };
}
// OR
#[no_mangle]
pub extern "C" fn free_array(ptr: *mut RubyArray) {
if ptr.is_null() { return }
unsafe { Box::from_raw(ptr); }
}
This results in an error:
Function 'free_array' not found in [/libfaster_path.so] (FFI::NotFoundError)
Here's the Struct I'm using and which gets created perfectly in to Ruby from Rust.
use libc;
use std::mem;
#[repr(C)]
pub struct RubyArray {
len: libc::size_t,
data: *const libc::c_void,
}
impl RubyArray {
#[allow(dead_code)]
pub fn from_vec<T>(vec: Vec<T>) -> RubyArray {
let array = RubyArray {
data: vec.as_ptr() as *const libc::c_void,
len: vec.len() as libc::size_t
};
mem::forget(vec);
array
}
}
But that's not relevant as it's not the issue. The issue is the method is not being made available in the library output for FFI to read from. What's wrong with this? Rust is happy and I've written many other methods in similar manner that work. What makes this not found in the .so file?
The file is included in the main src/lib.rs with pub mod so there's nothing wrong there. It's the same as the other working methods.
I'll be posting a blog with much fuller implementation details later and I'll add a link to the comment section below for those who are interested.
Minor Update
I'm pretty sure this is an issue with Rust. I've used both Ruby's Fiddle and FFI to verify that this method couldn't be accessed where as other methods could be by both.
I grepped the binary and found the text showing the free_array method in the binary but apparently that's not compiled correctly to be read by FFI.
The information I provided was not enough to debug the issue. The real problem was I had changed from building source from release to debug and had not updated the FFI linked library folder to reflect that. So the code kept pointing at and old lib.

Finding executable in PATH with Rust

In Python I can:
from distutils import spawn
cmd = spawn.find_executable("commandname")
I tried something like the code below, but it it assumes you're on unix-like system with /usr/bin/which available(also it involves execution of external command which I want to avoid):
use std::process::Command;
let output = Command::new("which")
.arg("commandname")
.unwrap_or_else(|e| /* handle error here */)
What is the simplest way to do this in Rust?
I found a crate that solves the problem: which. It includes Windows support, even accounting for PATHEXT.
I'd probably grab the environment variable and iterate through it, returning the first matching path:
use std::env;
use std::path::{Path, PathBuf};
fn find_it<P>(exe_name: P) -> Option<PathBuf>
where P: AsRef<Path>,
{
env::var_os("PATH").and_then(|paths| {
env::split_paths(&paths).filter_map(|dir| {
let full_path = dir.join(&exe_name);
if full_path.is_file() {
Some(full_path)
} else {
None
}
}).next()
})
}
fn main() {
println!("{:?}", find_it("cat"));
println!("{:?}", find_it("dog"));
}
This is probably ugly on Windows as you'd have to append the .exe to the executable name. It should also potentially be extended to only return items that are executable, which is again platform-specific code.
Reviewing the Python implementation, it appears they also support an absolute path being passed. That's up to you if the function should support that or not.
A quick search on crates.io returned one crate that may be useful: quale, although it currently says
currently only works on Unix-like operating systems.
It wouldn't surprise me to find out there are others.
Here's some ugly code that adds .exe to the end if it's missing, but only on Windows.
#[cfg(not(target_os = "windows"))]
fn enhance_exe_name(exe_name: &Path) -> Cow<Path> {
exe_name.into()
}
#[cfg(target_os = "windows")]
fn enhance_exe_name(exe_name: &Path) -> Cow<Path> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
let raw_input: Vec<_> = exe_name.as_os_str().encode_wide().collect();
let raw_extension: Vec<_> = OsStr::new(".exe").encode_wide().collect();
if raw_input.ends_with(&raw_extension) {
exe_name.into()
} else {
let mut with_exe = exe_name.as_os_str().to_owned();
with_exe.push(".exe");
PathBuf::from(with_exe).into()
}
}
// At the top of the `find_it` function:
// let exe_name = enhance_exe_name(exe_name.as_ref());

Resources