Puzzling visibility issues with Rust tuple-like structs
Asked Answered
O

1

5

I am encountering a puzzling error when creating a tuple-like struct somewhere in my Rust project.

The error

I boiled my issue down to the following snippet:

mod parent {
    pub struct X(u32);

    mod child {
        use crate::X;

        fn f() {
            let x = X(42);
        }
    }
}

use parent::X;

In my head, this snippet should compile just fine. X is public, and its field is visibile within parent, so all its children (including child::f) should be able to access it. However, when I go and compile that snippet, I get

error[E0423]: expected function, tuple struct or tuple variant, found struct `X`
 --> src/main.rs:8:21
  |
8 |             let x = X(42);
  |                     ^

For more information about this error, try `rustc --explain E0423`.
error: could not compile `playground` (bin "playground" test) due to 1 previous error

Weirdly enough, the error message mentions the compiler expecting a "tuple variant", but finding X instead. But, X is a tuple variant!

Path dependence

The problem disappears when I use super::X instead of crate::X.

mod parent {
    pub struct X(u32);

    mod child {
        use super::X;

        fn f() {
            let x = X(42);
        }
    }
}

use parent::X;

This is even more puzzling to me. Why would the correctness of my snippet depend on the specific path I use to use X? In my mind, X is X, wherever I import it from.

With non-tuple structs

The problem also disappears when I give X named fields:

mod parent {
    pub struct X {
        x: u32,
    }

    mod child {
        use crate::X;

        fn f() {
            let x = X { x: 42 };
        }
    }
}

use parent::X;

Note that I am using use crate::X in this snippet, just as in the first one! Why would this error appear only with tuple-like structs? I am very confused. I am usually not one to ever doubt the correctness of the Rust compiler, but I have to admit that today my faith is being tested.

Any insight on what I might be missing?

Oe answered 17/9 at 11:20 Comment(1)
With pub struct X(pub u32); the first example compiles, so the issue is the visibility of the field. Though I don't understand the inconsistency between fields of a tuple struct and named fields.Lemcke
H
6

This is the same problem as in #74139.

Imports filter away names that are private at the import point, that's a pretty fundamental rule. The Foo's constructor is private to mod a, so it's filtered away.

So this is not a bug, but the error should probably have a note mentioning the private constructor. Such note is emitted in some other cases but not in this one.

petrochenkov commented Jul 8, 2020

Only the private function X (each tuple struct implicitly defines a constructor function in addition to the struct itself) is filtered out so using regular struct syntax does work (for both struct types):

mod parent {
    pub struct X(u32);

    mod child {
        use crate::X;

        fn f() {
            let x = X{ 0: 42 };
        }
    }
}

use parent::X;

Playground

Hermineherminia answered 17/9 at 11:47 Comment(5)
Why is the Foo constructor private if Foo is public? Why does this affect only module b but not a?Unjaundiced
Interesting that record syntax is available while there are private fields. I'd say it's a bug.Devisor
@Unjaundiced the constructor is private because at least one field is private. Module a directly uses Foo where the function constructor is available, so it isn't filtered.Hermineherminia
@Unjaundiced struct X(u32) declares two things: a struct named X, and a function (a constructor) named X that has the signature fn(u32) -> X. The former is public but the latter is private.Devisor
@ChayimFriedman I believe that is because the record syntax is inseparable as an item from the struct, so it has to be there, this bit is public. The field access is only detected on usage. The function is a distinct item so it can be filtered out.Hermineherminia

© 2022 - 2024 — McMap. All rights reserved.