Joining separated modules to isolate inner attributes

AFAICT, there isn't a language supported way to do the following:

We currently have a single module, part of which is user supplied code, part of it is generated code returning user supplied types. Currently these are stuffed into in one module which we can't really change the layout of. But we've split them up like so, to be able to set some inner attributes on the generated code.

mod _user_ {
    // We would like to add the ability for users to specify inner attributes here.
    use std::error::Error;  // Comes from user supplied text with a bunch of other stuff.

    mod _generated_ {
       #[allow(unsafe_code)]
       use super::*;
        fn foo() {
            unsafe { () } 
        }
       fn bar() -> Box<dyn Error>{ "bar".into() }
   }
   use _generated_::*;
}

Ideally there would be a way to split these up into sibling modules, like so, so that their inner attributes are completely isolated from one another.

mod _user_ {
    #![deny(unsafe_code)] // User supplied
    use std::error::Error;
}

mod _generated_ {
    fn foo() {
        unsafe { () } 
    }
   
    pub use super::_user_::*;
    fn bar() -> Box<dyn Error>{ "bar".into() }
}

use _generated_::*;

The way it currently works requires the special behavior of use super::* to import use statements from the outer module without the user specifying pub use. But this also leaves the inner module at the mercy of the outer modules inner attributes.

AFAICT there has been quite a few discussions of anonymous modules, the most similar being the ones where things such as use mod { ... }::*; were discussed. I'm not really sure I have any good ideas. The current overloading of use to have different behaviors for super and other paths without any syntactic visibility distinction leaves a bit of a void, when one wants the behavior of super for other paths.

Would people think that if it were a thing use mod { ... }::* should import an inner use ?

I assume the problem with the second example is that use super::_user_::*; doesn't allow access to items in _user_ that aren't marked as pub.

super really isn't special at all. There's no inconsistency here. What you're seeing is that submodules can access private items of parent modules, but siblings cannot access private items of sibling modules.

I assumed there is something more to it because pub mod _user_ { } to change the default visibility of the module still doesn't work. But this may just be use statements not using the default visibility of the module?

Either way, however it works the submodule/parent relationship is producing 2 behaviors the first pertaining visibility, and the other is the scope of inner_attributes. super may not be special, but it would be nice if there was a way to disentangle it from the other behavior.

pub mod does not change the default visibility of the module, it changes the visibility of the module itself. It doesn't have any effects on the visibility of the items within.

The only other way is to make every item in _user_ also pub.

Right the problem is we can't control the user code, for backwards compatibility. The two modules have previously been one single module. With user code due to it's placement in the module unable to specify inner atrributes.

It would be nice if I could lift this restriction while maintaining backwards compatibility.

Unfortunately it is just not possible to access private items of a sibling module. And this is not going to change because libraries use the property to enforce certain invariants.

Where does the user code come from? Could you not modify the user code automatically to insert pub before every use and make every other item pub?

The user code is specified in a yacc like format, the specific problematic case (towards the bottom) of this file:

the generated code generates a function parse which returns a Result<u64, Box<dyn Error>> as specified in the file.

Avoiding changing the behavior of visibiity for current module usage is why I was hoping that something like: use mod { ... }::* might allow this kind of anonymous module where we want to specify inner attributes behavior without breaking everything everywhere.

couldn't easily add pub to the user code here without actually parsing the rust code, which isn't really an option. I also think if it were an option there would be concerns about the nature of changing visibility (just on principle).

For this specifically, I've written some macros which construct a “one higher” pub restriction. Specifically, doing some expansion in the vein of

// from
macro! {
    struct Priv;
    pub(super) struct Sup;
}

// into
mod __user {
    use super::*;
    pub(in super) struct Priv;
    pub(in super::super) struct Sup;
}
mod __gen {
    use super::*;
    // …
}
pub use self::{__user::*, __gen::*};

and you don't need to tweak non-relative visibility restrictions. This does of course require understanding the Rust code.

...no? You get the exact same behavior from use super::*; or use crate::path::to::parent::module::*;. super is just a shortcut name, not some special mechanics.


I think the simplest solution for compatibility in your tool would be to put user code in a module if any inner attributes are used (can just be a string search for lines starting with #![ after trim, that works for rustdoc), and no module if not. It'll be a visibility quirk users will need to deal with, but not a crazy surprising one.

If you build in some sort of epoch selection to your tool you can migrate to always using a module, which I think is likely the “correct” approach; the grammar is logically a separate unit from whatever module it's mounted onto.

I think that this might be a workable option I hadn't even considered, I'll definitely give it a try thank you.

The point is use has behavior dependent upon the path when it is relative to the current module or not. And there is no mechanism for the definer of the module to select a behavior without employing a module heirarchy, or modifying visibility within the module contents. Or if not use being responsibile, the visibility is dependent upon path relativity. Apologies if I seem frustrated, I view whether visibility or, use or super to be responsible seems like semantics, when ultimately it's behavior I'm concerned with.

In theory at least there are other cases where default visibility has also been an issue: in particular enum, has default visibility of pub for variants, while structures are private.

That is to say: something like enum default_visibility(pub in self) { ...} mod default_visibility(pub in super) { ... } would be another to approach this if use mod { ... } for some reason isn't an option people like.

The codegen already works based upon editions, so it is perfectly reasonable to only support this feature with a specific edition of the language in the future. I would personally prefer it if it was something as simple as use mod { ... } or setting a default visibility on a module declaration. Over putting the contents of the module through a visibility translation macro.

No offense, but this sentence is completely unreadable to me as written.

1 Like

I tried to fix it, I was a tad frustrated when I wrote it.

I think I can interpret: Given

mod user {
  // arbitrary items, not under the control of the authors of
  // the rest of the code
}
mod system {
  // stuff
}

@ratmice's request is to add some kind of knob that allows mod system to refer to private items within mod user, as if system were a submodule of user, but has no other effects on anything. By vague analogy to C++ friend classes, I propose we call this knob "friend modules" and spell it thus:

mod user {
  #![friend_mod = super::system]
  // arbitrary items, not under the control of the authors of
  // the rest of the code
}
mod system {
  use super::user::*;  // imports private items as well as public
  // stuff
}

If it's opt-in by the exporting module in this fashion, I don't see any way this can break anything that already exists.

2 Likes

Also, I believe @pitaj and @ratmice are talking past each other because of the following conceptual confusion: What @ratmice thinks of as "use behaves differently dependent on [whether the importing module is a submodule of the exporting module]", @pitaj thinks of as having nothing to do with use at all.

Rather, @pitaj would describe the same phenomenon as a property of the default visibility rules: in the absence of a visibility modifier, an item is visible to the module that defines it and all of its submodules, and not to any other module. This naturally means that use path::* imports a different set of items depending on where it is -- but the same thing happens for

mod bar {
  pub mod foo {
    pub fn a() {}
    pub(super) fn b() {}
  }
  pub fn test() { 
    use foo::*;
    a();
    b();  // no error here
  }
}
pub fn test() {
  use bar::foo::*;
  a();
  b();  // error[E0425]: cannot find function `b` in this scope
}

Have I understood each of you correctly?

Yes, for my part I consider it a behavior of use because items of super modules aren't imported by default, e.g.

mod bar {
   struct Foo;
   mod foo {
       // Error, because it was never brought into scope by `use`.
       // I didn't want to say default visibility because it isn't visible here by default.
       fn foo() -> Foo { Foo }
   }
}

So that use is also subject to default visibility rules, was a failed attempt to simplify the description of the problem. But @zackw has nailed down fairly well both what I was wanting in his friend description. And how I approached explaining it well.

When we refer to visibility in Rust, we generally refer to whether a given item can be accessed via any path. I think we generally use the term "in-scope" to refer to items that are available directly within a given scope.

That said, I think it would be kinda cool to be able to set the default visibility for a module or even a whole crate.

1 Like

Sounds like we're on the same page.

Anyhow I couldn't come up with any form of decent syntax for it which I felt fits nicely in the language. I think probably the right thing syntactically might be a default_visibility built-in attribute