# Struct with read-only and write-only properties

**URL:** <https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179>\
**Category:** General Usage\
**Tags:** struct, mutable-structure, constructors, properties\
**Created:** [May 20, 2025, 2:52pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179 "2025-05-20T14:52:10Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![mattsignorelli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mattsignorelli/32/221502_2.png) [@mattsignorelli](https://discourse.julialang.org/u/mattsignorelli)\
**Post date:** [May 20, 2025, 2:52pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179/1 "2025-05-20T14:52:10Z")

</div>

Suppose I have the following mutable struct

```julia
@kwdef mutable struct Rectangle{T}
  x::T = 0
  y::T = 0
  function Rectangle(args...)
    return new{promote_type(typeof.(args)...)}(args...)
  end
end

```

I would like for this struct to have two additional properties, 1) `area` which is read-only, and 2) `square` which is write-only and sets both `x` and `y`:

```julia
function Base.getproperty(r::Rectangle, key::Symbol)
  if key == :area
    return r.x*r.y
  elseif key == :square
    error("Property :square is write-only for Rectangle")
  else
    return getfield(r, key)
  end
end

function Base.setproperty!(r::Rectangle{T}, key::Symbol, val) where {T}
  if key == :area
    error("Property :area is read-only for Rectangle")
  elseif key == :square
    r.x = val
    r.y = val
    return val
  else
    return setfield!(r, key, T(val))
  end
end

```

Even though this is a mutable struct, I have a general framework which only creates new promoted structs if the type of a field is set to a value which requires type promotion. This makes use of the super powerful Accessors.jl . E.g.

```julia
r = Rectangle(1.0, 2.0)
r.x = 1im # error because r isa Rectangle{Float64}

using Accessors
@reset r.x = 1im # works!

```

The problem is if a user tries to (re)set a the write-only field including promotion:

```julia
r = Rectangle(3.0,4.0)
r.square = 5.0 # fine
r.x == r.y == 5.0 # true

@reset r.square = 2.0im
# ERROR: ArgumentError: Failed to assign fields (:square,) to object with fields (:x, :y).

```

With this error, and with some reading of ConstructionBase.jl documentation, I currently have two questions:

1. How should I override `Base.propertynames` for the case where some properties are write-only and some read-only?
2. The documentation suggests that I should override `ConstructionBase.setproperties` (and `ConstructionBase.getproperties` ?) however in the documentation for these functions I see the requirement:

```julia
    3. setproperties is defined in relation to getproperties so that:
       obj == setproperties(obj, getproperties(obj))

```

How do I handle this when some properties are write-only and some read-only?

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 20, 2025, 8:24pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179/2 "2025-05-20T20:24:00Z")

</div>

I don’t think there is a lot of precedent for “write-only” properties, are you sure that’s what you need?

If you _really_ need them, overloading `propertynames()` to return rw + r properties and `setproperties()` to support rw + w properties should just work correctly. I’m pretty sure the ConstructionBase docs (including the requirement you cite) didn’t anticipate writeonly properties at all 🙂

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [May 21, 2025, 2:23pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179/3 "2025-05-21T14:23:10Z")

</div>

> [@aplavin](#):
>
> I don’t think there is a lot of precedent for “write-only” properties

Write-only memory is usually a joke because there’s no point in writing data that will never be read, but not when considering permissions. For example, you might let participants enter their names and addresses into a raffle, but it’s obviously bad to let any participant see the full list. In a programming language, the relevant concept is access modifiers.

There are plenty of OOP examples of public properties with only (dot syntax) setters that change the value of a private property so that only the class’s methods can use that private property freely. Granted, those examples are also often explicitly qualified to have very little use because it’s fairly difficult to make a setter’s inputs that’s unable to replicate what a getter does. For example, I don’t need `r.square` to get a value `r.square = 5.0` I just set. Also, Julia only has modules for true encapsulation and doesn’t have formal access modifiers, so those OOP examples don’t translate well. Instead, `public`-ness regarding types is specified by API and leans more to methods than properties.

It’s also worth considering whether something _is_ a property. All rectangles have an area (and even then Julia would normally use an `area(::Rectangle)` method), but rectangles don’t have squares (which is why a getter doesn’t make sense), rather some rectangles are squares. Making a square rectangle is as simple as `Rectangle(x) = Rectangle(x, x)` or `r.x = r.y = z`, no need to try to justify a setter.

> [@aplavin](#):
>
> I’m pretty sure the ConstructionBase docs (including the requirement you cite) didn’t anticipate writeonly properties at all 🙂

Weirdly it almost does with [function lenses](https://juliaobjects.github.io/Accessors.jl/stable/examples/custom_optics/). Strictly speaking, it’s properties of an alternate type representing an equal value (in the linked example, a 2D Cartesian `Point` vs a polar `NamedTuple`), but the `@set` syntax resembles setting a write-only property.

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [May 21, 2025, 3:40pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179/4 "2025-05-21T15:40:10Z")

</div>

I will strongly second the recommendation to make a function `area(::Rectangle)` rather than overriding `getproperty` to support `r.area`. In Julia especially, an accessor function is much more powerful, simple, flexible, extensible, and reusable than `getproperty`/`setproperty!`.

I only occasionally use `object.field` outside of defining accessors (and never in a function where `object` is not specifically dispatched as an input argument). In my early days of Julia, I would frequently use `object.field` in code but sometimes (surprisingly often, actually) would have to make tedious revisions as my project evolved. Accessor functions have been much easier to maintain and expand, which is why I use and recommend them.

I would handle your example like this:

```julia
area(r::Rectangle) = r.x * r.y

function square!(r::Rectangle, val)
  r.x = r.y = val
  return r # or `return val` or `return nothing` or whatever you want
end

```

* * *

Sidenote: I seldom use `mutable struct` (\<5% of the objects I create). A (immutable) `struct` is usually easier to work with and tends to perform better (even when “changing” it means you must rebuild the whole thing with some fields changed rather than simply changing them – the compiler is pretty good at this). I would definitely make an object like the example’s `Rectangle` immutable in almost every circumstance.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 21, 2025, 3:48pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179/5 "2025-05-21T15:48:14Z")

</div>

> [@Benny](#):
>
> > [@aplavin](#):
> >
> > I’m pretty sure the ConstructionBase docs (including the requirement you cite) didn’t anticipate writeonly properties at all 🙂
> 
> Weirdly it almost does with [function lenses](https://juliaobjects.github.io/Accessors.jl/stable/examples/custom_optics/).

Function lenses are great and everything, but you are linking a different package here – which is higher level than ConstructionBase 🙂  
Basically, the idea of Accessors.jl is that `@set f(x) = newval` should work for every `x` and `f` wherever it makes sense. It’s a very powerful and widely applicable concept indeed!

---

<div class="post-metadata">

**Author:** ![mattsignorelli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mattsignorelli/32/221502_2.png) [@mattsignorelli](https://discourse.julialang.org/u/mattsignorelli)\
**Post date:** [May 21, 2025, 4:19pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179/6 "2025-05-21T16:19:00Z")

</div>

Thanks to everyone for your detailed replies. I certainly agree that this approach is not the most Julian on the surface. But let me explain the use case and I would greatly appreciate input.

I have one higher level struct which _may_ have an enormous amount of different properties. So the first idea is to do something like:

```julia
mutable struct MyStruct
  x
  y
  lorem
  ipsum
  dolor 
  ... # tons of possibilities
end

```

The problem with this approach is that (1) most instances of this structure will not need most of these properties, (2) the properties can have different types so every get is type unstable even if some properties are very related, and (3) it is not extensible if I or another user later wants to add another property, I have to change the entire struct.

To solve/improve these three issues, I instead have done the following:

1. Group related properties into substructures (e.g. `Rectangle`)
2. `MyStruct` has one field: a dictionary with the key being the type of the substructure, corresponding to a value that is the substructure
3. override `Base.getproperty(::MyStruct, ...)` and `Base.setproperty!(::MyStruct, ...)` to identify the substructure group which the property symbol belongs to, and call `getproperty`/`setproperty!` on that corresponding substructure.

This solution makes it so that I can get chunks of related properties from the MyStruct with only one type instability. e.g., Now `mystruct.Rectangle` (which in my framework is equal to `mystruct.substructure_dict[Rectangle]`) gives me the corresponding `Rectangle{T}` (type unstable step), but at least from there I can now get both `x` and a `y` type-stably. In the first flattened implementation, `mystruct.x` and `mystruct.y` are separately two type unstable gets rather than just one. I can also always add more new substructures later. E.g. maybe I want to add a `Dog`:

```julia
@kwdef mutable struct Dog{T}
   mass::T = 0
   height::T = 0
end

```

But users can also do: `mystruct.x = 5.0`, or `mystruct.area` and still get the behavior as if it were the first idea above. And if users do `mystruct.mass = 187`, then I will create a new `Dog` and put it in the struct on the fly. It’s like a quasi-flattened structure optimized for flexibility and performance. And if you do something like `mystruct.x = 5.0im # promotion`, then I detect this and use `Accessors.jl` to replace the current substructure with a promoted one. It is fully polymorphic and gives excellent performance in optimization loops, in fact often fully equal to an alternative bits representation. I therefore am quite happy with this implementation and struggle to find a better way (your input greatly appreciated!).

For this reason, my end users will expect to just do `mystruct.x` for both setting and getting all information from this struct, rather than doing `mystruct.x` for some information and `specialgetter(mystruct)` and `specialsetter!(mystruct, val)` for others.

The full implementation is here if you are curious: [GitHub - bmad-sim/Beamlines.jl: Fast, flexible, and polymorphic/differentiable beamlines](https://github.com/bmad-sim/Beamlines.jl)

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [May 21, 2025, 6:46pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179/7 "2025-05-21T18:46:44Z")

</div>

I’m afraid I don’t precisely follow what you’re describing and the linked repo was too large for me to find further clarification. But I can assure you that getters/setters are no _less_ powerful than `getproperty`/`setproperty!`. I’ll admit that one additional reason I don’t modify properties is that _it’s just such a pain to do._

* * *

But I have some other concerns about (_my flawed understanding of_) your design. Maybe not all of these concerns are relevant to your intended uses.

Fundamentally, I don’t see the appeal of making `mystruct.x` magically refer to `mystruct.substructure_dict[:Rectangle].x`. The flattened structure does not seem more flexible, convenient, or performant to me (and you already decided it was better to step back one level from it by only making it _appear_ flat). What if I have two substructures with the `x` property? How does it decide which to read/write? I think `Rectangle(mystruct).x` is much more clear, which you can enable by simply defining

```julia
Rectangle(a::MyStruct) = a.substructure_dict[Rectangle]
# personally, I would use `Symbol`s for keys rather than `DataType`s

```

Similarly, while you can define

```julia
area(a::MyStruct) = area(Rectangle(a))

```

I don’t think `area(mystruct)` is more clear than `area(Rectangle(mystruct))`. And again, what it two substructures have an `area` attribute?

Your current key scheme offers no way to put two `Rectangle`s into a `MyStruct`. But that seems to not be something you need, so never mind.

Honestly, all the code to introspect every element of `substructure_dict` to find where it can hopefully read/write something (which might be ambiguous!) seems like much more effort and much more fragile than simply making getters for those elements. All the magic that the introspection introduces seems unnecessary and, while it might save the user a few keystrokes when the code is written, will cause extensive head-scratching when that code is read and maybe bugs when it’s run (if there are ambiguities).

---

<div class="post-metadata">

**Author:** ![mattsignorelli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mattsignorelli/32/221502_2.png) [@mattsignorelli](https://discourse.julialang.org/u/mattsignorelli)\
**Post date:** [May 21, 2025, 7:15pm UTC](https://discourse.julialang.org/t/struct-with-read-only-and-write-only-properties/129179/8 "2025-05-21T19:15:16Z")

</div>

> [@mikmoore](#):
>
> What if I have two substructures with the `x` property? How does it decide which to read/write?  
> …  
> And again, what it two substructures have an `area` attribute?  
> …  
> maybe bugs when it’s run (if there are ambiguities).

Just like if the structure were actually flattened, I cannot have two fields with the same name. So at the level of `MyStruct` properties with the same name can only point to one substructure. By design ambiguities cannot happen.

> [@mikmoore](#):
>
> Your current key scheme offers no way to put two `Rectangle`s into a `MyStruct`. But that seems to not be something you need, so never mind.

In my application this is not allowed.

> [@mikmoore](#):
>
> Honestly, all the code to introspect every element of `substructure_dict` to find where it can hopefully read/write something

This is not the implementation, it is much simpler. Developers of the substructures can add to a dictionary its fields/properties which they would like to be accessible at the `MyStruct` level. `Base.getproperty` of `MyStruct` goes to that dict with the symbol it receives. There is no introspection of every element (this of course would be very slow…).

My end users are not career programmers. They are focused on physics. As such, one single interface for getting/setting any information from the substructures is strongly preferred. If I have to explain to them that, “For getting/setting these certain properties, you must use these functions, and for getting/settings these other properties, you can use the usual dot-syntax”, then that may be confusing…

Furthermore in my implementation, the performance of doing `mystruct.substructure_dict[Rectangle].x` is very similar to doing `mystruct.x`. In my Beamlines.jl package for example:

```julia
julia> using Beamlines, BenchmarkTools

julia> ele = LineElement();

julia> @btime $(ele).L
  47.654 ns (0 allocations: 0 bytes)
0.0f0

julia> @btime $(ele).pdict[UniversalParams].L
  34.000 ns (0 allocations: 0 bytes)
0.0f0

```

For my use case, this get time (order of 10 ns) is much, much smaller than the simulation time (microseconds - seconds), and so the difference between 48 and 34 ns is acceptable with that considered.
