Thread overview
FYI: Be careful with imports when using public:
Oct 10, 2023
Jonathan M Davis
Oct 11, 2023
Salih Dincer
Oct 11, 2023
Jonathan M Davis
Oct 11, 2023
Salih Dincer
Oct 11, 2023
Jonathan M Davis
Oct 11, 2023
user1234
Oct 11, 2023
mw
October 10, 2023
I just thought that I'd point this out, since I got bit by it, and others might be in the same boat, but apparently, if you have code such as

    struct Foo
    {
    public:
        import std.range.primitives;
    ...
    }

then the import is treated as public (and the same goes if you use public: at the module level and have module-level imports under it). In this particular case, the confusing result is that trying to call functions in std.range.primitives (such as walkLength or moveFront) on an instance of Foo using UFCS will result in the code not compiling (whereas using the normal function call syntax works just fine), and the error messages don't make it all clear as to why, so it took me quite a while to figure out what the problem was.

In retrospect, it seems like it probably should have been obvious that using public: would affect all imports below it in that scope, just like it does with any of the symbols in that scope, but since I rarely do anything with public imports, I tend to forget that they're a thing, and it had never occurred to me that public: could affect imports (or if it had, I completely forgot).

So, for those of you who use public: and private: (rather than marking each symbol individually with public or private) should be careful to make sure that no imports are under a public: unless that's what you actually want (which you probably don't, since that's rarely desirable; usually, that sort of thing would only be done in a package.d module).

I don't know if the issue that I had with UFCS is expected (probably), but the larger issue is of course that you don't want to accidentally make imports public - especially in libraries - so clearly, anyone using public: needs to be careful with their imports (or just mark symbols with public or private individually instead, which has its own pros and cons, but it won't result in accidentally making imports public, so that would definitely be one of its pros).

- Jonathan M Davis



October 11, 2023

On Tuesday, 10 October 2023 at 07:43:26 UTC, Jonathan M Davis wrote:

>

... In this particular case, the confusing result is that trying to call functions in std.range.primitives (such as walkLength or moveFront) on an instance of Foo using UFCS will result in the code not compiling (whereas using the normal function call syntax works just fine), and the error messages don't make it all clear as to why, so it took me quite a while to figure out what the problem was.

I didn't experience any compilation errors or import problems either. Moreover, I also tried selective import. For example:


--main.d--
import publicCase;

import std.stdio;
void main() {
  auto s = S(" No Problem ");
  s.wordCount.writeln(": ", s);
  // 2: S(" No Problem ")
}

--publicCase.d--
module publicCase;

struct S
{
  private:
    string str;

  public:
   import std.range.primitives;// : walkLength;
   import std.algorithm;// : splitter;

  size_t length() => str.length;
  size_t wordCount() => str.splitter.walkLength;
}

SDB@79

October 11, 2023
On Tuesday, 10 October 2023 at 07:43:26 UTC, Jonathan M Davis wrote:
> I just thought that I'd point this out, since I got bit by it, and others might be in the same boat, but apparently, if you have code such as
>
>     struct Foo
>     {
>     public:
>         import std.range.primitives;
>     ...
>     }


I always try to put all my imports at the top of the source files, as long as there is no conflicts -- the same practice as I do in Python.

For the reason to avoid any unexpected consequences, e.g. as you described.

October 10, 2023
On Tuesday, October 10, 2023 9:53:07 PM MDT Salih Dincer via Digitalmars-d wrote:
> On Tuesday, 10 October 2023 at 07:43:26 UTC, Jonathan M Davis
>
> wrote:
> > ... In this particular case, the confusing result is that trying to call functions in std.range.primitives (such as walkLength or moveFront) on an instance of Foo using UFCS will result in the code not compiling (whereas using the normal function call syntax works just fine), and the error messages don't make it all clear as to why, so it took me quite a while to figure out what the problem was.
>
> I didn't experience any compilation errors or import problems either. Moreover, I also tried selective import. For example:
>
>
> ```d
>
> --main.d--
> import publicCase;
>
> import std.stdio;
> void main() {
>    auto s = S(" No Problem ");
>    s.wordCount.writeln(": ", s);
>    // 2: S(" No Problem ")
> }
>
> --publicCase.d--
> module publicCase;
>
> struct S
> {
>    private:
>      string str;
>
>    public:
>     import std.range.primitives;// : walkLength;
>     import std.algorithm;// : splitter;
>
>    size_t length() => str.length;
>    size_t wordCount() => str.splitter.walkLength;
> }
> ```
>
> SDB@79

The problem is not using the imports inside of the struct where you have the public import (since whether imports are public or not has no impact on the part of the code where they're imported, just on other modules importing that code). The problem is using them on the struct from within another module. So, if you have something like

a.d
---
struct Range
{
public:
    import std.range.primitives;

    int front() { return _i; }
    void popFront() { ++_i;}
    bool empty() { return _i == 10; }

private:
    int _i;
}

main.d
------
void main()
{
    import std.range.primitives;
    import a;
    auto l = Range.init.walkLength();
}

This will result in

main.d(5): Error: none of the overloads of template
`std.range.primitives.walkLength` are callable using argument types `!()()`
/usr/local/include/dmd/std/range/primitives.d(1767):        Candidates are:
`walkLength(Range)(Range range)`
/usr/local/include/dmd/std/range/primitives.d(1795):
`walkLength(Range)(Range range, const size_t upTo)`

- Jonathan M Davis



October 11, 2023

On Wednesday, 11 October 2023 at 05:31:33 UTC, Jonathan M Davis wrote:

>

The problem is not using the imports inside of the struct where you have the public import (since whether imports are public or not has no impact on the part of the code where they're imported, just on other modules importing that code). The problem is using them on the struct from within another module.

I see! In this situation there are 2+1 (the first one doesn't count) solutions if I do not remember wrong:

The first thing is not to use "public:" but still be careful. Because the imports may accidentally be in scope, as you mentioned.

Other is "Renamed Imports", a local name for an import can be given. For example:

  public:

     import std.range.primitives : wl = walkLength;

Finally, use selective import again and define it inside the member function. For example:

  public:

    size_t length() {
      import std.range.primitives : walkLength;
      return this.walkLength;
    }

Thank you very much for this information. There is a huge difference between "import" alone and "public import"!

SDB@79

October 11, 2023
On Wednesday, October 11, 2023 5:34:50 AM MDT Salih Dincer via Digitalmars-d wrote:
> On Wednesday, 11 October 2023 at 05:31:33 UTC, Jonathan M Davis
>
> wrote:
> > The problem is not using the imports inside of the struct where you have the public import (since whether imports are public or not has no impact on the part of the code where they're imported, just on other modules importing that code). The problem is using them on the struct from within another module.
>
> I see! In this situation there are 2+1 (the first one doesn't count) solutions if I do not remember wrong:
>
> The first thing is not to use "public:" but still be careful. Because the imports may accidentally be in scope, as you mentioned.
>
> Other is "Renamed Imports", a local name for an import can be given. For example:
>
> ```d
>    public:
>
>       import std.range.primitives : wl = walkLength;
> ```

I wouldn't advise that, since I'm pretty sure that that results in having wl as a public member of the type that it's in (or of the module that it's in if you do it at at the module-level), and you presumably don't want to be creating any extra public symbols like that. In general, simply making sure that the import is before public: solves the issue without having to jump through any hoops - or using public as an attribute rather than as a label also solves the problem.

> Finally, use selective import again and define it inside the member function. For example:
>
> ```d
>    public:
>
>      size_t length() {
>        import std.range.primitives : walkLength;
>        return this.walkLength;
>      }
> ```

In general, it's advised to scope imports as tightly as possible to avoid what they impact (as well as to make it clearer where they're used so that you know where symbols come from and when you can remove the imports when refactoring), but you do sometimes need them outside of functions (e.g. for the return types), so while local imports help, they don't always solve the problem.

Personally, I ran into this issue, because I just wanted to have the necessary import to get the range primitives for arrays without having to worry about where within the member functions I was using them, and it would have been fine if I'd put the import before the public:, but I didn't, so I ran into issues.

> Thank you very much for this information. There is a huge difference between "import" alone and "public import"!
>
> SDB@79

Ultimately, what it comes down to is that if you use public:, you need to be careful where you place imports in relation to it. You don't want to be creating public imports by accident (and almost never want to create them on purpose; package.d is the primary counter-example). Of course, you can choose to just avoid public: to avoid the problem (in which case, presumably you just use public as an attribute directly on the symbols), but the point is that if you do use public:, you need to be careful where you put it. And it's easy to forget that it impacts imports, since most of us don't do much with public imports.

- Jonathan M Davis



October 11, 2023

On Wednesday, 11 October 2023 at 11:34:50 UTC, Salih Dincer wrote:

>

Other is "Renamed Imports", a local name for an import can be given. For example:

  public:

     import std.range.primitives : wl = walkLength;

other is to use a from instance alias and to fully qualify your accesses:

module m;

alias from_prims = from!"std.range.primitives";

void v(T)(ref T t)
{
     const wl = from_prims.walkLength(t);
     ...
}

that way you can just ignore whether the alias is public or not.
(note: yes I know that this also implies that UFCS has to be sacrificed)