When to use crate:: vs proj_name:: in imports? - rust

I understand that one can import functionality within the project in 3 different ways:
Using project name
use proj_name::mod1::mod2::fn_name;
Using crate
use crate::mod1::mod2::fn_name;
Using a relative path
use mod1::mod2::fn_name;
What I'm confused about is that it seems that sometimes the compiler wants me to use (1) and sometimes (2) / (3). I can't figure out the ruleset for which should be used when. Can someone help?

Only the crate itself is allowed to refer to itself as The crate. Every single dependant uses its name prefix. That includes binary targets within the same crate. They might be coupled together in the project, but they are NOT part of library crate.
Binary targets in fact are all separate crates for all purposes of the build system that work the same as libraries that have lib.rs at their root and writing crate in them refers to root of that specific binary target.
Demo:
#[derive(Debug)]
pub struct A;
mod b {
#[derive(Debug)]
pub struct B(pub crate::A);
}
fn main() {
println!("{:?}", b::B(A));
}
Playground

Related

Why can a no_std crate depend on a crate that uses std?

In the example, hs reexport HashSet from std. But it compiles without error or warning. Why?
#![no_std]
pub use hs::HashSet;
pub fn new() -> HashSet<usize> {
HashSet::new()
}
pub fn insert(a: &mut HashSet<usize>, v: usize) {
a.insert(v);
}
Well #![no_std] just means that you don't include std by default. It doesn't mean you can't explicitly or implicitly (i.e. through other crates) still include std. In other words #![no_std] does not prohibit using std but it disables using it by default.
For example this works, too:
#![no_std]
extern crate std;
fn main() {
std::println!("hello!");
}
Every crate in Rust is opt-in, that is, you need to explicitly ask it as a dependency to have it as a dependency of your code (usually through Cargo.toml). All, but one: the standard library (hence the name), which is opt-out. That is to say, if you do nothing, Rust will implicitly understand you require it as a dependency, and you explicitly need to tell it that you don't (with #![no_std]) to prevent this. Besides this, std behaves like any other crate, that is, even if it's not one of your direct dependencies, you can still access to reexports of your actual dependencies.
It should be noted, however, that there is no reason to not want to standard library as a dependency except in situations where you just don't have it (for instance, if you are writing embedded code, or a kernel, or ...). In this case, having std as a (transitive) dependency will just fail at compile time, which means that not only you have to use #[no_std], but so should all the dependencies you have, and their dependencies, and so on... In your case, you have annotated your code with #[no_std], but there was no hard requirement for you to do so, so there is no problem in having a dependency that has access to std.
Finally, note that some parts of the standard library are accessible even if the whole standard library is not. For instance, core is not OS-dependent, so you can use it even on embedded, and usually obtaining alloc one way or another (that is, implementing what is required yourself for instance) also provides you quite a lot of the standard library. In particular, I think HashSet only requires core and alloc to be available, not the whole standard library.

Cannot find [package] in the crate root

How do you get rust to recognize a module within a crate? I believed it was enough to declare mod [module name] in lib.rs. Is it not?
Error:
error[E0432]: unresolved import `crate::a`
--> src/bin/b.rs:2:12
|
2 | use crate::a::f;
| ^ could not find `a` in the crate root
src/a.rs:
pub fn f() {}
src/bin/b.rs:
use crate::a::f;
fn main() {}
src/lib.rs:
mod a;
Cargo.toml:
[package]
name = "m"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "b"
path = "src/bin/b.rs"
[dependencies]
You are confusing crates and packages. As per the corresponding chapter in the book:
A crate is the smallest amount of code that the Rust compiler considers at a time. [...] A crate can come in one of two forms: a binary crate or a library crate.
This means that your lib.rs and bin/b.rs files define two separate crates and thus crate refers to different things. Both belong to the same package which is defined by your Cargo.toml file. To use functions from your library crate in your binary crate, use the crate name instead of crate. In your case, the crate name is the same as the package name, which is m. Note that you will have to mark your library items as pub to use them in another crate.
An example of this can be found in the bat program, which uses both crate and bat in the imports of the bat binary crate:
https://github.com/sharkdp/bat/blob/2dbc88d3afdacf6449f299c70f29e5456904779b/src/bin/bat/main.rs
When a binary is compiled it is seen as the root of the project structure. So lib.rs is not compiled for binary targets and you can not use any of its modules unless you also add them in your binary. The most common approach is to put your binaries in the src folder with lib.rs so you don't need to worry about the folder structure when using your modules. I suppose you could use include!("lib.rs"); at the top of your binary to make it inherit everything from your library, but I have not tried it and it would likely be considered bad form.
However, you can sidestep the issue by making your binary depend on your library. The easiest way to do this is by making your binary its own crate which depends on your library.
There are also some ways to get around this, is by making a crate a dependency of itself, but these solutions always make me feel a little uneasy about the stability of the solution. This answer can give you a general idea of what that looks like.
Here are some examples various solutions. In case you are wondering, these are just the first crates I found that fit the design pattern. I do not know what some of them do/contain.
https://github.com/stanislav-tkach/os_info (Workspace members)
https://github.com/sagiegurari/cargo-make (Binaires in source)
https://github.com/hominee/dyer (Subcrates (dyer-macros))
https://github.com/clap-rs/clap (Example binaries)

How can I use a trait implementation from another crate in the original crate?

I have a Rust project which divided among several crates in a workspace. One of these crates is a test crate which holds utilities for use in unit and integration tests within the other crates.
In one crate I define a trait that is implemented by a struct in the test crate. However, when I try to use the struct from the test crate in the original crate which defined the trait, I encounter errors when trying to use the trait's member functions.
Here is a short example:
In the project-trait crate:
trait Trait {
fn do_something(&self)
}
In the project-test crate:
use project_trait::Trait;
pub struct TestObject;
impl Trait for TestObject {
fn do_something(&self) {
// ...
}
}
Finally, back in the project-trait crate:
#[cfg(test)]
mod test {
use crate::Trait;
use project_test::TestObject;
#[test]
fn use_test_object() {
let object = TestObject;
object.do_something();
}
}
When running cargo test in the project-trait crate, I get error E0599 which says I should import project_trait::Trait to use the do_something method. It seems Rust doesn't see that crate::Trait and project_trait::Trait are the same trait.
Is there any workaround for this?
There can't be cycles in the crate dependency graph. Therefore, whatever you've actually done in your project configuration, it can't be that the #[cfg(test)] code that is depending on project-test is using the same crate::Trait as what project-test sees as project_trait::Trait.
You have probably done something that causes some of the code to be compiled as part of a different crate (such as misuse of mod so as to compile the same source file in two crates). The result of this is that you have two different traits that happen to both be named Trait. Thus, you get an error because the one that TestObject implements is not the same as the one you have imported.
(In order to get a more precise explanation of what's going wrong, you'll need to provide a more complete example — preferably reproducible, i.e. "if I copy these files into a local folder they will be sufficient to demonstrate the problem".)
In general, whatever code is needed to run project-trait's tests must be part of the project-trait crate or its dependencies — you can't split out test helpers into an independent library crate.

Use dependency of a dependency? [duplicate]

I want to use the dijkstra function from the pathfinding crate:
pub fn dijkstra<N, C, FN, IN, FS>(
start: &N,
neighbours: FN,
success: FS
) -> Option<(Vec<N>, C)>
where
N: Eq + Hash + Clone,
C: Zero + Ord + Copy,
FN: Fn(&N) -> IN,
IN: IntoIterator<Item = (N, C)>,
FS: Fn(&N) -> bool,
To use it I need to implement the Zero trait from the num_traits crate. But how can I import Zero? An obvious way is to add extern crate num_traits; into my crate and fix my Cargo.toml appropriately. But in doing so, I have to watch a dependency of a dependency, which is not good.
Can I somehow implement Zero without explicit dependency on the num_traits crate, like below?
use pathfinding::num_traits::Zero;
Given the original intent of importing non-exposed dependencies from a crate (such as pathfinding) into a dependent project, that is currently not allowed. If a dependency is not re-exported by the crate, that makes it more of an implementation detail than part of the API. Allowing a dependent to access any "sub-dependency" would therefore be catastrophic.
In this case however, since num_traits is clearly used in the crate's public API, it also makes sense for the dependent to have access to it. As it is, you are expected to add the dependency in your own project, while taking care to keep a compatible version. Otherwise, cargo might end up building duplicate dependencies.
[dependencies]
num_traits = "0.1"
In order to avoid this, pathfinding would benefit from exporting its own num_traits, as below. PR #6 was created for this purpose, and has been merged into version 0.1.12 (thanks, #SamuelTardieu).
pub extern crate num_traits;
With that done, you can now do exactly as written at the end of your question:
use pathfinding::num_traits::Zero;

Consistent TypeIds for dynamically loaded crates

I have a plugin system where I pass &dyn Any to a dynamically loaded rust function, but downcasting the reference fails because the TypeIds differ (for the same type), although I added rustflags = ["-Cmetadata=12345678"] to both crates' cargo config. Also it seems as if only types from external crates are affected (I tried () and it yielded the same TypeId in both crates). I am currently casting the raw pointers (unsafe { &*(v as *const dyn Any as *const Type) }) to work around this issue, but I would prefer a solution without unsafe code.
For example the following code:
println!("CRATE 1: TypeId of `()`: `{:?}`, TypeId of `toml::Value`: `{:?}`",
TypeId::of::<()>(), TypeId::of::<toml::Value>());
produces this output:
CRATE 1: TypeId of `()`: `TypeId { t: 7549865886324542212 }`, TypeId of `toml::Value`: `TypeId { t: 9270396907601429078 }`
CRATE 2: TypeId of `()`: `TypeId { t: 7549865886324542212 }`, TypeId of `toml::Value`: `TypeId { t: 5704635987193303200 }`
EDIT:
This does not seem to be a problem with different dependency versions, as crate 2 (which is dynamically loaded) depends on crate 3 (which is also dynamically loaded) and the problem still occurs, although both, crate 2 and crate 3, are local dependencies (so there is only one version). Crate 1 is btw. the crate that loads crate 2 & 3.
EDIT:
I removed the -Cmetadata option from all 3 crates and now I get the same TypeId for toml::Value, however the TypeId of a type in crate 1 that I want to downcast in crate 2 still differs.
After some testing I found out that the TypeIds are different because the loader crate (crate 1) is used as a library in the other 2 crates but executed as a binary.
To work around the issue I extracted all of crate 1's types I wanted to use in the loaded crates to a new crate and added it to each crate's dependencies. This new crate is only ever used as a library and not a binary, thus the TypeIds should be consistent.
A summary of everything I had to do to get it working:
use the same toolchain version for all crates
use the same dependency versions in all crates
do not use -Cmetadata, this doesn't work anymore and has in fact the opposite effect
extract all types that are used in the loader crate and the loaded crates into a new crate and add it to all crates' dependencies
whenever you change something in this 'common types crate' you have to recompile all the other crates, so the TypeIdS are up to date
If you still have a problem you can fall back to unsafe rust (e.g. unsafe { &*(value as *const dyn Any as *const Type) }). Note that there are no checks at all, so you might encounter segmentation faults if the types don't match.
Special thanks to #trentcl for pointing me in the right direction.

Resources