# Puzzling construction with executable statements in struct definition

**URL:** <https://discourse.julialang.org/t/puzzling-construction-with-executable-statements-in-struct-definition/136887>\
**Category:** General Usage\
**Tags:** struct\
**Created:** [April 26, 2026, 7:33am UTC](https://discourse.julialang.org/t/puzzling-construction-with-executable-statements-in-struct-definition/136887 "2026-04-26T07:33:41Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [April 26, 2026, 7:33am UTC](https://discourse.julialang.org/t/puzzling-construction-with-executable-statements-in-struct-definition/136887/1 "2026-04-26T07:33:42Z")

</div>

I thought I had seen most things in julia, but I’ve come across the following construction which I don’t think I’ve seen in the docs:

```julia
using MacroTools
const gotit = Set()

macro genconstructor(spec)
    if !@capture(spec, function name_{T__}(args__) where TV__ body_ end)
        error("error")
    end

    ename = esc(name)
    newspec = quote
        if !$(name in gotit)
            $(push!(gotit, name))
            function $ename(args...; kw...)
                call_some_constructor(args...)
            end
        end
    end

    quote
        $(esc(spec))
        $newspec
    end
    
end

struct Foo{T}
    a::T

    @genconstructor function Foo{T}(x) where T
        Foo(x)
    end

    @genconstructor function Foo{T}(x::T) where T
        Foo(x+1)
    end
end

julia> methods(Foo)
# 1 method for type constructor:
 [1] Foo(args...; kw...)

julia> methods(Foo{Int})
# 2 methods for type constructor:
 [1] Foo{T}(x::T) where T
 [2] Foo{T}(x) where T

```

I.e. the macro `@genconstructor` takes a function, a constructor, looking like

```julia-auto
function Foo{T}(args) where T 
... 
end

```

It outputs the same function definition, and conditionally, a slightly different definition, so the full output of the macro is two constructors, the original and a simplified one:

```julia-auto
function Foo{T}(args) where T 
... 
end
if !(:Foo in gotit)
    push!(gotit, :Foo)
    function Foo(args...; kw...)
        dosomething()
    end
end

```

The point with the test and the `gotit` `Set`, is to avoid defining the extra constructor repeatedly if the macro is used more than once in the same struct (which would lead to a warning, and prevent precompilation).

Now, my question is, what are the rules for executing statements inside a struct definition? Can local variables be made there? Is it just like any other local scope, except for definition of functions named like the struct, which becomes inner constructors?

Are things like this officially supported?:

```julia
struct Bar
    a::Int

    local b = Ref(0)
    Bar(n) = (e = new(n+b[]); b[] += n; e)
end
julia> Bar(1)
Bar(1)

julia> Bar(1)
Bar(2)

julia> Bar(1)
Bar(3)

julia> Bar(1)
Bar(4)

```

---

<div class="post-metadata">

**Author:** ![eldee](https://avatars.discourse-cdn.com/v4/letter/e/b5a626/32.png) [@eldee](https://discourse.julialang.org/u/eldee)\
**Post date:** [April 26, 2026, 8:52am UTC](https://discourse.julialang.org/t/puzzling-construction-with-executable-statements-in-struct-definition/136887/2 "2026-04-26T08:52:51Z")

</div>

As far as I understand, all the explanation on `@genconstructor` only serves as an example to show that you can have e.g. branches in a struct definition? If so, I think you could make the question clearer by moving this into a collapsible

> **Motivation**
>
> or something similar

and just using a simpler MWE.

> [@sgaure](#):
>
> Now, my question is, what are the rules for executing statements inside a struct definition? Can local variables be made there?

I don’t have deep insights into how everything works under the hood, but my impression is that you can do anything you would inside a `let` block (i.e. [with hard scoping rules](https://docs.julialang.org/en/v1/manual/variables-and-scoping/#man-scope-table)), with the notable exception that new variables have to be declared `local` to distinguish them from fields (which of course can be declared in contrast to in `let` blocks).

E.g.

```julia-repl
julia> bool = nothing
       value = 3.16
       struct S
           s # or s::..., becomes a field

           local square = value * value
           global bool = square > 10 # updates the outside variable bool
           if bool
               S() = new(1)
           else
               S() = new(0)
           end
       end

julia> bool
false

julia> S() # (Note: if you now increase value without redeclaring S, S() will remain S(0).)
S(0)

julia> struct S2
           s

           square = value * value
           S2() = new(square)
       end
ERROR: syntax: "square = (value * value)" inside type definition is reserved around REPL[5]:1
Stacktrace:
 [1] top-level scope
   @ REPL[4]:1

```

* * *

EDIT:

> [@sgaure](#):
>
> Is it just like any other local scope, except for definition of functions named like the struct, which becomes inner constructors?

Seems you have updated the question while I was typing, but yeah, that sounds about right 🙂 , except for the requirement of having to use `local`. Whether this is officially supported I can’t say.

* * *

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [April 26, 2026, 9:22am UTC](https://discourse.julialang.org/t/puzzling-construction-with-executable-statements-in-struct-definition/136887/3 "2026-04-26T09:22:11Z")

</div>

Interesting. It means that it’s possible to create structs with fully hidden “fields”, but with accessor functions using local variables. Not that I would recommend it, but it might have its uses as struct level variables. Like class variables, not object variables. I wonder if these local variables are at all visible?

```julia-auto
struct A
    local a::Float64 = 0.0
    A() = new()
    A(::Val{:seta}, x) = a = convert(Float64, x)::Float64
    A(::Val{:geta}) = a
end
Base.getproperty(a::A, ::Symbol) = A(Val(:geta)) # some checks can be done here
Base.setproperty!(a::A, ::Symbol, val) = A(Val(:seta), val)

julia> a = A()
A()
julia> a.a = 2.0
2.0
julia> a.a
2.0

```
