Rustc whining about trait scope when it's in scope


#1

The way I have my project files set, I have lib.rs include!() all the other source files, because… it seems confusing otherwise. I recently tried making a trait to extend the byteorder crate’s functionality by implementing read/writes for my structs. No matter what I try though, rustc complains the trait isn’t in scope:

vector3.rs:

pub trait ReadVector3: ReadBytesExt
{
  #[inline]
  fn read_v3<T: ByteOrder>(&mut self) -> Result<Vector3>
  {
    let mut buf = [0; 12];
    self.read_exact(&mut buf)?;
    Ok(Vector3
    {
      x: T::read_f32(&buf),
      y: T::read_f32(&buf),
      z: T::read_f32(&buf),
    })
  }
}

pub trait WriteVector3: WriteBytesExt
{
  #[inline]
  fn write_v3<T: ByteOrder>(&mut self, n: Vector3) -> Result<()>
  {
    let mut buf = [0; 12];
    T::write_f32(&mut buf, n.x);
    T::write_f32(&mut buf, n.y);
    T::write_f32(&mut buf, n.z);
    self.write_all(&buf)
  }
}

bounding_box.rs:

pub trait ReadBoundingBox: ReadVector3
{
  #[inline]
  fn read_bb<T: ByteOrder>(&mut self) -> Result<BoundingBox>
  {
    let mut buf = Cursor::new([0; 24]);
    self.read_exact(buf.get_mut())?;
    Ok(BoundingBox
    {
      min: buf.read_v3::<T>()?,
      max: buf.read_v3::<T>()?,
    })
  }
}

pub trait WriteBoundingBox: WriteVector3
{
  #[inline]
  fn write_bb<T: ByteOrder>(&mut self, n: BoundingBox) -> Result<()>
  {
    let mut buf = Cursor::new([0; 24]);
    buf.write_v3::<T>(n.min)?;
    buf.write_v3::<T>(n.max)?;
    self.write_all(&buf)
  }
}

lib.rs:

extern crate byteorder;

use byteorder::{ByteOrder,ReadBytesExt,WriteBytesExt};
use std::io::{Cursor,Result};

include!("bounding_box.rs");
include!("vector3.rs");

#2

You haven’t implemented any of your traits for any types. You can only invoke a trait’s methods on types that implement that trait. Check out Implementing a Trait on a Type.

If you want for example ReadVector3 implemented for all types that implement std::io::Read, that would look like:

impl<R: Read + ?Sized> ReadVector3 for R {}

#3

Hm. When I looked at byteorder’s docs, it seemed automatic, which, me being kind of new to Rust, thought it was implicit. Additionally, if I’m supposed to do that, there’s a problem. I tried implementing a trait once before for the f32 type and rustc complained about it not being a type within my crate.


#4

This isn’t an implementation of ReadBytesExt; it’s just a requirement that any type you implement ReadVector3 for must already implement ReadBytesExt.

It’s not automatic; it’s just that byteorder implements ReadBytesExt for any type which also implements Read like so:

impl<R: io::Read + ?Sized> ReadBytesExt for R {}

so that when you use byteorder::ReadBytesExt, any types that already implement Read can use the stuff from ReadBytesExt.

When you implement a trait for a type, either the trait or the type (or both) must be in your crate. So you could implement, for example, Read for one of your types, or as dtolnay mentioned, you could implement ReadVector3 for someone else’s types.


#5

Yeah, I was trying to implement Hash for f32. As odd as it may seem, I know C# does it via deriving GetHashCode() from Object and it was part of the API I was trying to port.


#6

First of: don’t use include!() in that way, please!

This may be the way to do it in C, but include! is not the ‘rustic’ way.
There are very sensible reasons to separate your code in multiple files, e.g. programmer readability.
But if you wish to hide this internal organisation from your users, the ‘rustic’ way of doing it is with pub use, which re-exports the functions. Good example in this in the excellent StackOverflow Answer:

https://stackoverflow.com/questions/22596920/split-a-module-across-several-files#22597760

read more about Rust’s module system in the Book, 2nd ed.


second: this Rust-Internals thread discusses (at great length) why f32 doesn’t impl Hash.
tl;dr: It’s complicated because f32 has some edge-cases where equality is fishy, such as NaN != NaN.

The reason why you cannot impl Hash for f32 is due to the “Coherence Rules”.
To prevent conflicting impl’s, you can only impl something if either
A) you are the “boss” of the type being impl'd (e.g. Hash)
B) you are the “boss” of the type being impl'd for (e.g. f32)

Being “the boss” means that you define it in your crate.

These rules ensure that there can only ever be ONE location in the entire Rust world that defines the impl, so you’ll never get errors along the lines of "Bob impls hash for f32, but so do Alice, Joe, Henry and Eve, all are different, which one should I use?"
This is a good thing because your code can never “suddenly” stop compiling if you add a dependency. (e.g. you have Bob’s impl, but later add Alice’s dependency; the Coherence Rules prevent other people breaking your code.)


#7

I tried cleaning it up by using the module split method, but pub use seems… confusing. I want the lib to be its own module, not have every sub file it uses be its own module.


#8

It is perfectly fine to have multiple internal-only modules that are invisible to the user. It is a tool to help you, as the programmer, to keep an overview.

pub use acts as if the entire used module was defined at the spot where you wrote it.

The StackOverflow link I posted earlier should also have a clear example in the top answer, and links to further reading.

Some more hopefully helpful reading:
https://aturon.github.io/blog/2017/07/26/revisiting-rusts-modules/
https://doc.rust-lang.org/book/second-edition/ch07-01-mod-and-the-filesystem.html

If you’re still struggling, can you give us some more information on where exactly you are confused? That’ll help us help you :slight_smile:


#9

I tried pub use myself last night but rustc began to complain about stuff not being in scope. This is what I put in place of the include!()s with no luck. Additionally, Cargo complained about being unable to find the byteorder, pod, etc crates I declared extern and had in Cargo.toml. Is extern crate only supposed to go in the main lib file or something? Because the modules that use these crates I’ve done extern crate and stuff like use byteorder::ByteOrder and what not.

pub use bounding_box::{BoundingBox,ReadBoundingBox,WriteBoundingBox};
pub use bounding_sphere::BoundingSphere;
pub use plane::{Plane,PlaneIntersection};
pub use quaternion::Quaternion;
pub use ray::Ray;
pub use vector3::{ReadVector3,Vector3,WriteVector3};

#10

Did you also declare bounding_box, bounding_sphere, etc as modules in lib.rs? Generally the lib.rs will look something like this:

extern crate byteorder;
// more extern crate here, as needed

// Declare your private modules
mod bounding_box; // compiler will look for bounding_box.rs or bounding_box/mod.rs
mod bounding_sphere;
//Optionally export a module as public
pub mod my_public_module;

// Make certain types in private modules public
pub use bounding_box::BoundingBox;
// the above makes BoundingBox available to your crate users without exposing bounding_box module itself.  So their code would look like:
// extern crate your_crate;
// use your_crate::BoundingBox;

#11

Another good look at modules is https://manishearth.github.io/blog/2017/05/14/mentally-modelling-modules/


#12

pub mod is it? Would module::type notation still be needed? I’m trying to make these types public at the root module (lib.rs) level


#13

If you write pub mod bounding_box then users of your crate are “aware” of your bounding_box module - when they want to use types from there, they’ll need to name bounding_box in their use statements.

If you, in your lib.rs, instead do:

mod bounding_box; // declare a private module
pub use bounding_box::*

Then this will export all public types from bounding_box as-if they were in your root module. So your crate users don’t know about bounding_box module, and instead they see the types you defined there as-if they were in the root module of the crate. So their code would look like:

extern crate your_crate;
// using your BoundingBox as an example of a concrete type that lives in bounding_box module inside your crate,
// but your crate users don't know that
use your_crate::BoundingBox;

The nice thing about how this works in Rust is you can expose types to your users under a completely different naming/module scheme than what you used internally in your crate. So you can organize the crate to your liking and then export a different scheme that makes sense for users of the crate. For example, this is how the “prelude” modules are usually done, which may have been mentioned upthread.

You can think of pub use as creating symlinks for your crate users - they don’t need to know about your internal module layout if you don’t want to expose that.


#14

That’s this part of vitalyd’s reply

At this point I’d recommend sleeping on it for a night, or going for a long walk. Anything to get some distance from the problem.
I get the impression that you are trying to use too many new ideas at once, so your brain loses the overview. That’s perfectly okay! Rust does a few things differently, and it’s focus on correctness can be very punishing until you get used to it. Module syntax also has some gotcha’s where we can still improve beginner-friendlyness, as Aturon discusses in his post. Vitalyd’s link about “mentally modelling” is also very good in explaining the ‘weird’ logic of pub, use and mod.

Everyone here on these forums has had a similar steep learning curve, so you’re not alone :heart:

(p.s. I love how discourse keeps interpreting the filename lib.rs as the (existing!) website https://lib.rs/. We can use backticks (`lib.rs`) to prevent this.)


#15

What would I put in each individual file then? (eg. bounding_box.rs, vector3.rs, etc)? extern crate and use clauses as well?


#16

You don’t need extern crate in them if you’ve already done that in lib.rs. You do need use clauses there to “import” the types from those crates that you’re going to use. Otherwise, bounding_box.rs (and the other modules) just implements all things bounding box.

I highly recommend you read that blog entry I posted earlier.


#17

I did. It seemed… Greek to me


#18

Then let it marinade a bit and come back to it again :slight_smile:.

Also, look at popular Rust libs and see how they structure the code. Come back here with questions. It’ll sink in, but requires patience.


#19

I did, actually. I double checked too before scratching my head. I did what you said too, but I’m still getting trait not found errors:

error[E0599]: no method named `write_v3` found for type `std::io::Cursor<[{integer}; 24]>` in the current scope
   --> src/bounding_box.rs:156:9
    |
156 |     buf.write_v3::<T>(n.min)?;
    |         ^^^^^^^^
    |
    = note: the method `write_v3` exists but the following trait bounds were not satisfied:
            `std::io::Cursor<[{integer}; 24]> : std::io::Write`
    = help: items from traits can only be used if the trait is implemented and in scope
    = note: the following trait defines an item `write_v3`, perhaps you need to implement it:
            candidate #1: `vector3::WriteVector3`

Code is basically this and I have the import as use vector3::{ReadVector3,Vector3,WriteVector3};:

pub trait ReadVector3: ReadBytesExt
{
  #[inline]
  fn read_v3<T: ByteOrder>(&mut self) -> Result<Vector3>
  {
    let mut buf = [0; 12];
    self.read_exact(&mut buf)?;
    Ok(Vector3
    {
      x: T::read_f32(&buf),
      y: T::read_f32(&buf),
      z: T::read_f32(&buf),
    })
  }
}

impl <T: Read + ?Sized> ReadVector3 for T {}

pub trait WriteVector3: WriteBytesExt
{
  #[inline]
  fn write_v3<T: ByteOrder>(&mut self, n: Vector3) -> Result<()>
  {
    let mut buf = [0; 12];
    T::write_f32(&mut buf, n.x);
    T::write_f32(&mut buf, n.y);
    T::write_f32(&mut buf, n.z);
    self.write_all(&buf)
  }
}

impl <T: Write + ?Sized> WriteVector3 for T {}

#20

It found the trait alright, but your type doesn’t match the constraint. You have impl<T: Write + ?Sized> WriteVector for T. But you apparently have a Cursor<[{integer}; 24]> that you’re trying to call the fn on. This cursor type doesn’t implement Write. It does implement it for Cursor<&mut [u8]> (among other types), which is close to what you’re trying to do apparently.

The error you have now has nothing to do with modules.