# Redefining structs without restart?

**URL:** <https://discourse.julialang.org/t/redefining-structs-without-restart/50826>\
**Category:** Internals & Design\
**Created:** [November 24, 2020, 5:08pm UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826 "2020-11-24T17:08:10Z")\
**Posts on this page:** 20\
**Page:** 2

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [November 25, 2020, 6:09pm UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/21 "2020-11-25T18:09:28Z")

</div>

> [@FedericoStra](#):
>
> could you elaborate on how this implies that redefining structures should be impossible

I am certainly not the wright person to elaborate on that. I just want to point out that the “solution” needs to deal with the names of the structs somehow, otherwise to what an “old” method would be specialized to? Seems that the name of the type is what defines the specialization.

Also it does not seem to be impossible, from the issue thread that I linked above, it just seems that there is no one available with the time and knowledge to do that exactly now.

---

<div class="post-metadata">

**Author:** ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)\
**Post date:** [November 25, 2020, 6:17pm UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/22 "2020-11-25T18:17:13Z")

</div>

I agree with you that it is not trivial to implement, nor is it completely clear and uncontroversial what the semantic should be. I simply believe that we should discuss what we expect the language to do, and just saying _“Oh it’s impossible because `@eval`”_ is not a very constructive way of exploring the landscape in my opinion.  
As a side note, in the REPL we are already allowed to redefine both modules and constants, and the behavior is not that far off from what I would expect from redefining structures:

```julia
const c = 0
f() = println(c)
f() # prints 0
c = 1
f() # still prints 0
f() = println(c) # forces an update
f() # prints 1

```

---

<div class="post-metadata">

**Author:** ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)\
**Post date:** [November 25, 2020, 6:34pm UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/23 "2020-11-25T18:34:39Z")

</div>

What _would_ you expect from redefining structures? That means redefining types, and what should happen to functions that already depend on that old type? Should they all be recompiled?

---

<div class="post-metadata">

**Author:** ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)\
**Post date:** [November 25, 2020, 6:57pm UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/24 "2020-11-25T18:57:44Z")

</div>

Ok, let me try to be very explicit with the words to avoid ambiguity.

When you say “redefining structures”, I interpret it as executing twice some code like `struct S ... end`. I do not interpret it as modifying the internal representation in Julia of an already existing type.

**One possible** way to deal with such event could be to define a **new** type called `S`, independent from the previous type also called `S`. In the namespace of the current module, the name `S` would now start referring to the new type instead of the old type. If you build an object of this new type and feed it to a function that was already specialized for the old type, then yes of course the function would have to specialize again! It is a new type after all.

For instance, in the hypothetical code

```julia
abstract type A end
f(::A) = 42
struct S <: A end
s1 = S()
f(s1) # returns 42
struct S <: A; x::Int end
s2 = S(0)
f(s2) # returns 42

```

`s1` and `s2` would be objects of different types, the old and the new `S`, and the function `f` should specialize for both of them.

Notice that we can already achieve something similar by wrapping the structures in a module and redefining the module:

```julia
abstract type A end
f(::A) = 42
module M struct S <: Main.A end end
s1 = M.S()
f(s1) # returns 42
module M struct S <: Main.A; x::Int end end
s2 = M.S(0)
f(s2) # returns 42

```

If you execute `methods(f).ms[1].specializations`, you see that `f` has two apparently identical specializations: one for the old `M.S` and one for the new `M.S`.

My expectation is just that this should work the same without wrapping in a module. I don’t think it’s realistic to expect something much different in this case.

* * *

Now, let’s move on to something slightly more controversial. Let’s say that instead of defining `f` for an abstract type `A`, we have instead a definition for `f(::S)`:

```julia
struct S end
f(::S) = 42

```

If we redefine `S`, should this method be applicable for the new `S`? If we perform the module trick as before

```julia
module M struct S end end
f(::M.S) = 42
s1 = M.S()
f(s1) # returns 42
module M struct S x::Int end end
s2 = M.S(0)
f(s2) # fails: no method matching f(::Main.M.S), closest candidate is f(::Main.M.S)...

```

we see that currently it fails with a cryptic error message. There is nothing inherently wrong with this behavior: the definition of `f` was intended for the old type `M.S`, and the new homonym type `M.S` is a different type.

**However** , from a usability perspective, one could argue that it is possible to expect the following: when `f` receives an argument of the new type `M.S`, it detects that it has a method applicable for the old `M.S` that was shadowed and specializes again that method for the new type. I’m not saying that this is the right thing to expect, but there is nothing insane or incoherent in expecting this behavior.

* * *

In conclusion, I think the behavior of the following code

```julia
struct S end
f(::S) = 42
struct S x::Int end
f(S(0))

```

is open to debate, but there are at least two reasonable behavior that one can expect: either `MethodError` because it is a new type, or specialize again because the new type is a redefinition of an accepted type (a sort of “retroactive dispatch”).

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [November 25, 2020, 11:13pm UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/25 "2020-11-25T23:13:15Z")

</div>

> [@FedericoStra](#):
>
> I don’t like much the implication of what you are trying to say here. I’m not being lazy and just complaining _“Oh Julia is so crappy compared to COBOL!”_ . I’m exploring outside the current semantic of the language and proposing options for possible extensions.

First off, sorry you took my comment this way, I certainly didn’t mean to imply that you’re lazy. I don’t take your comments so far as “just complaining”, rather as a little uninformed about why these long-known issues have not been solved (and likely won’t be in the foreseeable future).

> [@Will Julia ever fix its "using ..." latency problems?](https://discourse.julialang.org/t/will-julia-ever-fix-its-using-latency-problems/50659/79):
>
> As a side note, in the REPL we are already allowed to redefine both modules and _constants_, and the behavior is not that far off from what I would expect from redefining structures:

Note that you’ve misunderstood the semantics of `const` - it doesn’t mean that `c` will never change value, it means that `c` will never change _type_. It’s also not a guarantee the compiler gives you, it’s a _promise you make to the compiler_ about not changing value or type, so that it may optimize code that depends on that type:

```julia
julia> const c = 1
1

julia> c = 1.0
ERROR: invalid redefinition of constant c
Stacktrace:
 [1] top-level scope at REPL[2]:1

```

This is consistent with your observation that you have to “redefine” `f`. Since the binding was marked as `const`, the compiler inlined the value into the compiled code because it is bitstype and immutable.

* * *

> [@FedericoStra](#):
>
> The old structure still exists, you simply lose the possibility to refer to it by the same name in the current namespace, because that name now refers to the new structure.

Since `struct` literally defines a new type (of type `S`) and julia has nominative typing, “redefining S” quite literally means redefining the constant S referring to the type of the same name. Changing the type of a `const` binding is not allowed, because it would lead to having to recompile every piece of code that depends on `S`. You wouldn’t be able to do precompilation at all anymore, because any method might do `@eval ...` to change the structs in the containing module. You can sometimes get an error where a package does something like this and during precompilation you get the infamous `Precompilation may be fatally broken for this module` message. This is directly contradictory to the idea of making code fast by precompiling and caching, so I really don’t see how the seeming usefulness outweighs the benefit of losing precompilation.

The trick with the module only works because the seperate modules allow you to distinguish the two `S`. Modules don’t create a type:

```julia
julia> module M end
Main.M

julia> (s::M)() = "test"
ERROR: function type in method definition is not a type
Stacktrace:
 [1] top-level scope at REPL[2]:1

```

On the other hand, if you want to get rid of nominative typing, that’s an OK approach to solve the issue of redefinition - I just really doubt this is an approach that will be taken in julia because being sure what a name refers to is one of the most important things for any programming language.

> [@Will Julia ever fix its "using ..." latency problems?](https://discourse.julialang.org/t/will-julia-ever-fix-its-using-latency-problems/50659/79):
>
> I simply believe that we should discuss what we expect the language to do, and just saying _"Oh it’s impossible because `@eval` "_ is not a very constructive way of exploring the landscape in my opinion.

I for one think that giving examples and counterpoints to presented theorems is a valid way of conversation, but to each their own.

That said, `@eval` is the precise mechanism that makes it really hard to judge what a certain piece of code will do. If you allow shadowing of structs in global scope you also _have_ to handle `@eval`, since the following in your thought model is valid:

```julia
function f()
  @eval struct S end
end

function h()
   @eval struct S end
   @eval g(::S) = "from h"
end

f() # we got S
g(::S) = "example"
g(S()) # "example"
h() # we got S_1 and g(::S_1)
f() # we got S_2
s2 = S() # create an instance of s2
h() # we got S_3 and g(::S_3)
g(s2) # MethodError :(

```

`@eval` operates in global scope, so by allowing the shadowing of structs you can very easily create situations where you’d _expect_ a function call to work but it in fact doesn’t because the type is different even though their names are the same. Worse, such a function could come from any module you depend on, since (by your own example above) modules can add new methods to existing functions. This can suddenly and very unexpectedly change the behaviour of your code, simply by loading a dependency. That sounds like a very bad idea.

Now, as mentioned above, if we didn’t have `@eval`, we would not have this problem at all, which is why I brought it up as a counterpoint in the first place. That’s not a feasible thing to remove from the language though.

> [@Will Julia ever fix its "using ..." latency problems?](https://discourse.julialang.org/t/will-julia-ever-fix-its-using-latency-problems/50659/81):
>
> specializes again that method for the new type.

The compiler has no way of knowing how it _should_ specialize that function for the new type. From its point of view, there’s a method that takes a certain type X, that type X, and a new type Y which may or may not have the same name as X but with no way of knowing in what relation to each other they stand. The safe way is to not allow overwriting existing names. The slow way is to just insert dynamic lookups everywhere you access a value of that type. Specializing on types is the programmers job, so the only thing left to do is throw a `MethodError`.

---

<div class="post-metadata">

**Author:** ![josePereiro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josepereiro/32/17322_2.png) [@josePereiro](https://discourse.julialang.org/u/josePereiro)\
**Post date:** [November 26, 2020, 12:02am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/26 "2020-11-26T00:02:47Z")

</div>

Interesting!

```julia
$ time julia --startup-file=no -e 'using Plots'   
/usr/local/bin/julia-1.5.2 --startup-file=no -e 'using Plots' 24.06s user 0.90s system 86% cpu 28.871 total
$ time julia -O0 --compile=min --startup-file=no -e 'using Plots'
/usr/local/bin/julia-1.5.2 -O0 --compile=min --startup-file=no -e 13.71s user 0.90s system 92% cpu 15.720 total

```

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [November 26, 2020, 12:13am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/27 "2020-11-26T00:13:25Z")

</div>

I honestly don’t understand what’s the point of doing that. Loading `Plots` is faster, ok, but then any actual computationalyl-heavy operation is _slow_, so what’s the deal? Also, 24 seconds look _a lot_, it’s 8 seconds for me on Julia v1.5.3 (and I have a 5-year old laptop)

---

<div class="post-metadata">

**Author:** ![josePereiro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josepereiro/32/17322_2.png) [@josePereiro](https://discourse.julialang.org/u/josePereiro)\
**Post date:** [November 26, 2020, 12:18am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/28 "2020-11-26T00:18:27Z")

</div>

Well, I don’t know. People use python all the time and “ALMOST any actual computationally-heavy operation is slow”, so it must have it uses.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [November 26, 2020, 12:19am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/29 "2020-11-26T00:19:31Z")

</div>

Not really since all computationally-heavy code in Python eventually calls into C/C++/Fortran libraries

---

<div class="post-metadata">

**Author:** ![josePereiro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josepereiro/32/17322_2.png) [@josePereiro](https://discourse.julialang.org/u/josePereiro)\
**Post date:** [November 26, 2020, 12:22am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/30 "2020-11-26T00:22:11Z")

</div>

Yes, I know. But sometimes you just wants to plot results or move stuff around. It’s good to know that you can say “julia relax” I don’t need all your power right now!

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [November 26, 2020, 12:30am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/31 "2020-11-26T00:30:21Z")

</div>

Sure, good to know the option exists (and there are good use cases for `--compile=min`), but with Julia master I save about 0.7 seconds at startup by using `-O0 --compile=min`

```julia
Benchmark #1: julia-master --startup-file=no --project=. -e 'using Plots'
  Time (mean ± σ): 3.921 s ± 0.018 s [User: 3.995 s, System: 0.625 s]
  Range (min … max): 3.886 s … 3.938 s 10 runs
 
Benchmark #2: julia-master --startup-file=no --project=. -O0 --compile=min -e 'using Plots'
  Time (mean ± σ): 3.209 s ± 0.032 s [User: 3.322 s, System: 0.594 s]
  Range (min … max): 3.166 s … 3.261 s 10 runs
 
Summary
  'julia-master --startup-file=no --project=. -O0 --compile=min -e 'using Plots'' ran
    1.22 ± 0.01 times faster than 'julia-master --startup-file=no --project=. -e 'using Plots''

```

But plotting often comes associated with some number crunching, so saving less than one second at startup may not be that useful

---

<div class="post-metadata">

**Author:** ![josePereiro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josepereiro/32/17322_2.png) [@josePereiro](https://discourse.julialang.org/u/josePereiro)\
**Post date:** [November 26, 2020, 12:40am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/32 "2020-11-26T00:40:04Z")

</div>

Sure, by the way, how did you do those cool benchmarks?

---

<div class="post-metadata">

**Author:** ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)\
**Post date:** [November 26, 2020, 12:43am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/33 "2020-11-26T00:43:53Z")

</div>

As I see it, `const` is a _guarantee_ from the compiler that the type will not change (it throws an error), plus a _promise_ that I will not change the value, or that I’m ok to pay the price of some subtle errors if I try to do it (since I can).

In this light, I think that

```julia-auto
struct S ... end
module M ... end

```

should be thought of as the analogous of

```julia-auto
const S = struct ... end
const M = module ... end

```

The types cannot change (they must remain `DataType` and `Module` respectively), while the “values” can. The latter (the module) already works this way: if you execute `module M ... end` twice, you simply change the value of the constant `M`. This is exactly the same thing of doing `const c = 0; c = 1`.

Now, let me discuss your example with `f`, `g`, `h`, `S` and `@eval`. The functions `f` and `g` don’t play a significant role, so let me inline their body and get rid of `@eval`. Executing `struct S end` twice should be a no-op the second time, because we are not really changing the structure; so, to make the example more significant, let me use two different definitions of the structure. The example becomes

```julia-auto
struct S x::Int end # S_0
g(::S) = "example" # g(::S_0)
g(S(0)) # "example"

struct S x::Char end # S_1
g(::S) = "from h" # g(::S_1)

struct S x::Int end # S_2
s2 = S(0) # s2 isa S_2

struct S x::Char end # S_3
g(::S) = "from h" # g(::S_3)

g(s2) # ALERT!!!

```

As I mentioned at the end of my previous post, I can see at least two distinct ways to handle this situation. The first is lazier and purer on a conceptual level, the second is more convenient for an interactive user. I’ll explore the implications of both.

#### Option 1: old methods don’t apply to new shadowing types

We accept that the line marked `ALERT!!!` is a `MethodError`. I don’t see anything particularly bad about that. Just as changing the value of a `const c::Int = 0` can lead to errors, so may do wildly redefining structures. At least we have moved the error a bit forward, instead of throwing it on line 4 (at the first redefinition of `S`). A careful programmer could define a `g(::S)` at the right spot to deal with an argument of type `S_2` and everything would work. Actually, he is still in time to do

```julia-auto
g(::typeof(s2)) = "whatever"

```

and be able to add a methods that handles arguments of type `S_2`.

#### Option 2: old methods apply to new shadowing types (retroactive dispatch)

The other option is to recognize that there is a chain of overrides `S_0 -> S_1 -> S_2 -> S_3`. This could be represented by a property such as `shadows` or `overrides`, so that

```julia-auto
S_3.shadows == S_2
S_2.shadows == S_1
S_1.shadows == S_0

```

Now, whenever we have a function `g` with a method `g(::S)` that accepts an argument of type `S_1` and we feed in an argument of type `S_2`, we recognize that `S_2` was shadowing `S_1` and decide that the method is applicable. We then have to compile a new specialization specific for `S_2` and we are good to go.

This can be much more convenient for interactive use, but I also recognize that it can turn out to be quite messy.

* * *

### Conclusion

Both the approaches seem reasonable to me, and there may be many others. In both cases no dynamic lookup is required, and the behavior of `structs` becomes more consistent with that of `const`ants and `module`s.

* * *

### Addendum

I feel like an additional clarification is required. The name “nominal/nominative type system” might be a bit misleading. The equivalence and compatibility of types is **not** determined by the name to which they are bound in current namespace (`S` in the example before), but rather by their true identity as retrieved with `objectid`. Namely, it is ok to do

```julia-auto
struct S end
const T = S
g(::T) = "ok"
s = S()
g(s) # ok

```

because at the time of definition `T` was referring to `S`.

What I’m proposing is not incompatible with a nominal type system. It is basically just a better way to do the following

```julia-auto
struct _S1 end; S = _S1
g(::S) = "one"
struct _S2 end; S = _S2
g(::S) = "two"
# now g has two methods handling _S1 and _S2
```

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [November 26, 2020, 12:45am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/34 "2020-11-26T00:45:55Z")

</div>

With [hyperfine](https://github.com/sharkdp/hyperfine)

---

<div class="post-metadata">

**Author:** ![josePereiro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josepereiro/32/17322_2.png) [@josePereiro](https://discourse.julialang.org/u/josePereiro)\
**Post date:** [November 26, 2020, 12:53am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/35 "2020-11-26T00:53:53Z")

</div>

Cool, thanks!

---

<div class="post-metadata">

**Author:** ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)\
**Post date:** [November 26, 2020, 1:30am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/36 "2020-11-26T01:30:24Z")

</div>

Now that I think about it, the following macro almost does the trick:

```julia
macro redefinable(struct_def)
    struct_def.head == :struct || error("struct definition expected")
    if struct_def.args[2] isa Symbol
        name = struct_def.args[2]
        real_name = struct_def.args[2] = gensym(name)
    elseif struct_def.args[2].head == :<:
        name = struct_def.args[2].args[1]
        real_name = struct_def.args[2].args[1] = gensym(name)
    end
    esc(:( $struct_def; $name = $real_name ))
end

```

Now we can do

```julia
@redefinable struct S end
g(::S) = "old"
s1 = S()
@redefinable struct S x::Int end
g(s::S) = "new $(s.x)"
s2 = S(42)

g(s1) # "old"
g(s2) # "new 42"

```

The only thing which is missing is that the actual name of the structure is some garbage such as `var"##S#259"`. I could assign to `S.name`, but unfortunately if only accepts values of type `Core.TypeName`, which has `0` constructors… If we add a constructor `Core.TypeName(::Symbol)`, then the slightly modified macro (the one that sets the name too) implements precisely the semantic of my Option 1 above.

* * *

I modified the macro to support inheritance. Now we can even do

```julia
abstract type A end

g(a::A) = a.x # I give only a single method definition

@redefinable struct S <: A
    x::Int
end

g(S(42)) # 42

@redefinable struct S <: A
    x::Char
end

g(S('x')) # 'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

```

`g` of course has a single method, with two specializations (as can be checked with `methods(g).ms[1].specializations`).

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [November 26, 2020, 2:17am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/37 "2020-11-26T02:17:19Z")

</div>

> [@Sukera](#):
>
> Note that you’ve misunderstood the semantics of `const` - it doesn’t mean that `c` will never change value, it means that `c` will never change _type_ . It’s also not a guarantee the compiler gives you, it’s a _promise you make to the compiler_ about not changing value or type

> [@Will Julia ever fix its "using ..." latency problems?](https://discourse.julialang.org/t/will-julia-ever-fix-its-using-latency-problems/50659/93):
>
> As I see it, `const` is a _guarantee_ from the compiler that the type will not change (it throws an error), plus a _promise_ that I will not change the value, or that I’m ok to pay the price of some subtle errors if I try to do it (since I can).

I cannot say any of you are entirely right. [`const` makes the binding constant.](https://docs.julialang.org/en/v1/base/base/#const)

This means: if you do not want to incur in undefined behaviour, you should never ever do `x = anything` if `x` is a `const` variable. The _consequences_ of this are that: it will never change type, as you cannot change _which_ object is associated with `x`; the compiler may just inline the object inside of `function` that reference it, and so if you change it the function will continue displaying the old value; immutable objects with no mutable fields (basically all `isbits` objects) like most numbers cannot be changed in any way; if `x` is a a `mutable struct` like `Vector` then you can change its contents and size because `const` is not about the contents of the object but about the binding, that is, it will forever be the _same_ object (i.e., all other scopes/functions that reference it can be sure the “memory address we are speaking of” will never change). For these coming from C, the behavior for `mutable structs` is similar to a `const` pointer to an object, and for the immutable objects is like a `const` variable holding the object itself (without the pointer indirection).

---

<div class="post-metadata">

**Author:** ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)\
**Post date:** [November 26, 2020, 10:04am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/38 "2020-11-26T10:04:56Z")

</div>

I cannot say you are entirely correct either, I think.

* * *

> [@Henrique\_Becker](#):
>
> [`const` makes the binding constant.](https://docs.julialang.org/en/v1/base/base/#const)

No it does not:

```julia
const v = [1,2,3]
objectid(v) # 0xa41696f90596f3c7
const v = [1,2,3]
objectid(v) # 0xfdb41deeb4b28891

```

* * *

> [@Henrique\_Becker](#):
>
> if you do not want to incur in **undefined behaviour** , you should never ever do `x = anything` if `x` is a `const` variable.

I’m not sure it is undefined behavior in the same sense as in C. It shouldn’t and I hope it really isn’t. I guess it may just induce “erroneous or unexpected results”, not really undefined behavior, which is a much worse beast. By that I mean that the compiler is free to inline whenever it pleases any use of `const` values, therefore you can happen to witness simultaneously two different values of the same “constant”:

```julia
const c = 0
f() = c
f() # 0
c = 1
g() = (c, f())
g() # (1, 0)

```

* * *

Most importantly, you forgot to mention the last paragraph from [those very same docs](https://docs.julialang.org/en/v1/base/base/#const):

> In some cases changing the value of a `const` variable gives a warning instead of an error. However, this can produce unpredictable behavior or corrupt the state of your program, and so should be avoided. This feature is intended only for convenience during interactive use.

This whole “redefining structures business” is not meant to unleash wild programmers who want to build intricate packages based around savage use of `@eval`, it is meant primarily for interactive use. We should treat it as such: it can have some limitations and rough edges, but should nonetheless be more flexible than the current implementation, which doesn’t allow it.

---

<div class="post-metadata">

**Author:** ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)\
**Post date:** [November 26, 2020, 11:46am UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/40 "2020-11-26T11:46:16Z")

</div>

> [@FedericoStra](#):
>
> Now that I think about it, the following macro **almost** does the trick: […]  
> The only thing which is missing is that the actual name of the structure is some garbage such as `var"##S#259"` .

I worked around the limitation of the displayed name with the new macro

```julia-auto
macro redefinable(struct_def)
    struct_def isa Expr && struct_def.head == :struct || error("struct definition expected")
    if struct_def.args[2] isa Symbol
        name = struct_def.args[2]
        real_name = struct_def.args[2] = gensym(name)
    elseif struct_def.args[2].head == :<:
        name = struct_def.args[2].args[1]
        real_name = struct_def.args[2].args[1] = gensym(name)
    end
    esc(quote
        $struct_def
        $name = $real_name # this should be `const $name = $real_name`
        Base.show_datatype(io::Base.IO, ::Base.Type{$real_name}) = Base.print(io, $(QuoteNode(name)))
    end)
end

```

A small example:

```julia-auto
abstract type A end

foo(a::A) = (a, a.x)

@redefinable struct T <: A
    x::Int
end

@show foo(T(42))
# (T(42), 42)

@redefinable struct T <: A
    x::Float64
end

@show foo(T(3.14))
# (T(3.14), 3.14)

```

Again, this implements the semantic of Option 1: there are two independent types (secretly named `##T#***`) that know nothing about each other, and the name `T` in the current namespace switches from one to the other.

Now, there is still a problem with this macro: the third to last line should be `const $name = $real_name`, so that usage of `$name` (`T` in the example) would be inlined by the compiler.

* * *

#### Edit

In the macro, instead of the `Base.show_datatype` hack, one can assign to `$real_name.name.name`, that is, the return value of the macro becomes

```julia-auto
    esc(quote
        $struct_def
        $real_name.name.name = $(QuoteNode(name)) # fix the name
        $name = $real_name # this should be `const $name = $real_name`
    end)

```

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [November 26, 2020, 1:39pm UTC](https://discourse.julialang.org/t/redefining-structs-without-restart/50826/41 "2020-11-26T13:39:57Z")

</div>

Undefined behaviour include you being able to to alter the constant. I do not know where you took your definition of undefined behavior, but it means exactly that, the compiler is free to do whatever.

> [@FedericoStra](#):
>
> I guess it may just induce “erroneous or unexpected results”, not really undefined behavior, which is a much worse beast. By that I mean that the compiler is free to inline whenever it pleases any use of `const` values, therefore you can happen to witness simultaneously two different values of the same “constant”:

This paragraph makes no sense to me. You say undefined behavior is a much worse beast than “erroneous or unexpected results” then you give as an example of undefined behavior “that the compiler is free to inline whenever it pleases any use of `const` values” which is exactly what I said it could do in my reply and, at least to me, if the inlining happens and you manage to alter the `const` variable then the behavior of the program clearly falls into the category of “erroneous or unexpected results” .

> [@FedericoStra](#):
>
> Most importantly, you forgot to mention the last paragraph from [those very same docs](https://docs.julialang.org/en/v1/base/base/#const):

I did not forget. I omitted on purpose, because it falls under the definition of undefined behavior.

[Previous page](https://discourse.julialang.org/t/redefining-structs-without-restart/50826.md?page=1)

[Next page](https://discourse.julialang.org/t/redefining-structs-without-restart/50826.md?page=3)
