Does Julia Need a C++ style `static` keyword?

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.

1 Like

static variables are really just a sign that you should have a variable outside the object. The julian replacement is just a const global variable.

7 Likes

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.

The idiomatic answer to this is let, I believe:

let z = 0
    global function incrementZ()
        z += 1
        return z
    end
end

println(incrementZ())
println(incrementZ())
8 Likes

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.

5 Likes

Awesome! :partying_face:

I tried

let ...
  global struct MyType
  ...
  end
  ...
end

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 counter variable is not global, it’s in the let block so shouldn’t that be okay?

const counter = Ref(0)

2 Likes

yes but if do that you’ll keep getting warnings when you modify the reference.

e.g.

counter = Ref(1)
# WARNING: redefining constant counter
# Base.RefValue{Int64}(1)

counter = Ref(2)
# WARNING: redefining constant counter
# Base.RefValue{Int64}(2)

I guess you could do something like:

counter.x += 1

Sorry but it seems kind of hacky to me. Should const really be used for something like that?

It is global to the type (in the sense that various instances share the value).

If you absolutely need it, then go ahead and use this — but I think that mutable state is a bad habit, especially in Julia.

You do

julia> const counter = Ref(1)
Base.RefValue{Int64}(1)

julia> counter[] = 2
2

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?

Or is the idea that the reference is const but not the content? So you are dealing with a fixed point in memory?

precisely.

If I may ask, what are you trying to achieve? There may be a better way other than directly copying what you do in c++.

1 Like

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.

It is but I like it, it looks way cooler and more accomplished than ::MyType. :laughing:

I just considered that the let method is probably thread local and the const way may be the same across threads - is that right?