Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: How to re-export symbols in module hierarchy? #13979

Open
BryantLam opened this issue Sep 4, 2019 · 4 comments
Open

Discussion: How to re-export symbols in module hierarchy? #13979

BryantLam opened this issue Sep 4, 2019 · 4 comments

Comments

@BryantLam
Copy link

BryantLam commented Sep 4, 2019

Re-exporting is when a module makes available symbols defined in another module as if they were defined in that module.

  • to refactor your codebase while not making breaking API changes
  • to hide module hierarchy because you don't want it part of the API

Design discussion. Subthread under #13831 and #13978.

Motivation: Re-exports are used to expose a public interface that is different than the internal module hierarchy.

What is the behavior expected from re-exports? Is it sufficient to attach this behavior to public import and public use?

public module M {
  public proc fn() { writeln("fn"); }
}

module A {
  public import M only fn;
}

module B {
  public use M only fn;
}

/* Case 1 */ {
  import A;
  A.M.fn();
}
/* Case 2 */ {
  use A;
  M.fn();
}
/* Case 3 */ {
  import B;
  B.fn(); // M is hidden via facade
}
/* Case 4 */ {
  use B;
  fn();
}

While subject to change, these candidate behaviors seem reasonable to me because use and import statements would have their own intended use cases.


One concern I have is that if qualified access from use statements is not removed in #13978, re-exports could become confusing to learn because all public use locations would also have the qualified access re-exported as if a public import occurred too. For Case 4, it could be confusing that M.fn() would also work.

Though, one alternative path is to check if private import is used and prevent the qualified access: #13528 (comment). This approach would work, but it also feels like an unfortunate consequence that use statements also have qualified access because now the visibility of the module symbol is tied to a special case / precedence order of private import and public use.


Another approach is to consider the suggestions from Revisiting modules, take 3: make import statements only do imports and have some new reexport keyword to make desired symbols visible to other modules. The same could be applied to use statements.

Privacy specifiers on import/use statements are no longer necessary and it would be a clean separation of functionality because re-exporting wouldn't be tied to use or import statements anymore, ... but it also feels unnecessary if public import and public use are sufficient, especially with "learnability" precedence in Rust.

@lydia-duncan
Copy link
Member

One concern I have is that if qualified access from use statements is not removed in #13978, re-exports could become confusing to learn because all public use locations would also have the qualified access re-exported as if a public import occurred too. For Case 4, it could be confusing that M.fn() would also work.

Would limiting re-export of qualified naming to only when an explicit import is present be less confusing? E.g. for a set of modules defined like this

module A {
  var x: int;
}
module B {
  use A;
}
module C {
  import A;
}

would it be more or less confusing if C.x worked but B.x didn't?

I view the enabling of qualified access through use statements as a secondary concern, especially once import statements are implemented. While I like having it part of use statements for convenience, I wouldn't object to limiting it to only enabling qualified access in the scope in which it is defined, if that made it more palatable.

I think I would be okay with a reexport symbol in principle - I prefer having an explicit indicator that a symbol will be accessible via a different qualified naming path rather than having it be an additional property of something that is more focused on the current scope, though I am a little leery of adding so many keywords.

@mppf
Copy link
Member

mppf commented Sep 19, 2019

I wouldn't object to limiting it to only enabling qualified access in the scope in which it is defined

So the example would be this, right?

module A {
  var x: int;
  // A.x available because of current-module-name rule
}
module B {
  use A;
  // A.x potentially available here because of new "used symbol in scope defined" rule
}
module C {
  import A;
  // A.x is always available here
}
module BB {
  use B;
  // A.x is not available here
  // B.x is available here
}
module CC {
  import C;
  // C.A.x is available here
}

If that's the case, I'd consider adopting it. We could describe it as "A use statement always creates a private symbol referring to the module symbol itself to enable qualified access. The module symbol is not exported to other modules even if the use statement is public. In contrast, a public import statement will create a public symbol referring to the imported module, so that if module M contains (public) import A then a user/importer of M could access A itself.

Somehow I didn't follow this part:

Would limiting re-export of qualified naming to only when an explicit import is present be less confusing
would it be more or less confusing if C.x worked but B.x didn't?

Is there something wrong with the example? I might be just getting confused because it doesn't say where we are considering C.x or B.x working. I'm hoping that my variant on the example above seems like an obvious restatement of what you were saying...

@lydia-duncan
Copy link
Member

Yeah, that's the right interpretation, thanks!

@mppf
Copy link
Member

mppf commented Feb 5, 2020

Continuing this comment - #14407 (comment) - if a method is private (which we don't have yet, see #6067) - then I don't think it should be possible to use a re-export to make that method public again. The reason is that the method exists in a nested namespace (say the class on which it is defined) and since use / import will control namespaces/what is in scope, but not what methods are available, it won't make sense to allow re-export of the methods that case.

Of course re-export could be used to control visibility of the type with the methods.

I think this is O.K. because I think the purpose of re-exporting is to control namespace creep rather than to enforce public/private.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants