I was wandering whether there is currently a way of representing C++ style static variables in Julia. I haven’t found out how and I think they are pretty useful - especially when dealing with lots of object instances that share updatable properties.
C++ static keywords are used both within functions and within classes/types as system-wide but not necessarily global variables that can be updated for example https://www.studytonight.com/cpp/static-keyword.php
As with C++ it would also be useful to be able to specify whether the static variable is thread local or not.
Hmm const not a stand-in for C++ static. For a start something like this doesn’t work:
const z = 0
function incrementZ()
global z += 1
return
end
incrementZ()
# WARNING: redefining constant z
z
# 1
incrementZ()
z
# 1
If I’m honest I don’t use const because I don’t really know how it works. The manual (Essentials · The Julia Language) says const is for global variables that do not change - which is most certainly not the same idea as a C++ static variables. In addition I just managed to modify the global const variable z once which defeats the (manual’s) purpose of being constant. I guess I can make do with a regular global variable:
z = 0
incrementZ() # now works as expected.
But it doesn’t really “travel around” with your objects.
Thanks. That certainly looks useful and the variable is encapsulated away. In the case of classes I guess I can do something like:
mutable struct MyObject
name::String
age::Int64
end
let population = 0
global function MakeMyObject(name::String, age::Int64)
population += 1
return MyObject(name, age)
end
global function getPopulation(::Type{<: MyObject})
return population
end
end
paul = MakeMyObject("Paul", 42)
getPopulation(MyObject) # 1
peter = MakeMyObject("Peter", 21)
getPopulation(MyObject) # 2
Since we can’t declare MyObject(name::String, age::Int64) outside the object declaration, we can do something like this, which is a bit awkward but still useful:
function MyObject((name, age)::Tuple{String, Int64})
return MakeMyObject(name, age)
end
paul = MyObject(("Paul", 42))
getPopulation(MyObject) # 1
peter = MyObject(("Peter", 21))
getPopulation(MyObject) # 2
You can use an inner constructor (eg in the example below, with validation):
let counter = 0
global get_counter() = counter
struct Foo
x::Int
function Foo(x)
x > 0 || error("invalid x")
counter += 1
new(x)
end
end
end
which works like this (note counter is only incremented when you create a valid Foo):
julia> get_counter()
0
julia> Foo(1)
Foo(1)
julia> get_counter()
1
julia> Foo(-1)
ERROR: invalid x
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] Foo(::Int64) at /tmp/foo.jl:7
[3] top-level scope at REPL[41]:1
julia> get_counter()
1
That said, global mutable state is difficult to reason about and may preclude some compiler optimizations, so it is generally avoided in Julia. Instead of porting code in C++ style, I would recommend finding another solution.
and got an error so I assumed that you couldn’t have globally accessible structs inside the let block - but you’ve just shown that you can. Many thanks!
But the instances (of the objects) don’t actually share the value - it’s actually encapsulated away from the objects themselves and exists only in that let scope. My main worry would be if there was any performance issue with doing it this way, other than that I would feel comfortable with it. I’m pretty sure that I don’t want it as a global variable like const would be.
As for mutable vs immutable I don’t worry too much about it as long as I can decide and that it is clear when I do decide. Sometimes like this I definitely want mutability, at other times I don’t. I also think having something as const and mutating it in what looks like an underhanded way seems worse. It’s sending two conflicting messages to the reader - that something is constant but not really. If something is a constant shouldn’t it really be a constant?
The counter example is most relevant - sometimes you want to associate some property with all objects of a specific type whose value can be changed/fetched with a method/function and in C++ static is the way it’s done. In addition, the variable is the same for all the objects.
I still find my self gravitating towards @Tamas_Papp’s solution because it’s a variable that’s basically hidden from global and can only be interfaced via methods and can be used in a similar way to static variable but it itself is still encapsulated from the type that uses it and you can enforce it with a type parameter by including ::Type{<:Mytype} with the access method/function. Seems ideal to me.
As an aside, to me this seems like a much more cumbersome way to write
function example(::MyType)
[...]
end
Type constraints don’t have to be explicitly told that they’re a type. This is equivalent since the MyType in your example is a struct and can’t be subtyped anyway. In julia, only abstract type types can be subtyped.