Conventions for mutable shared structure

question

#1

I have a function which takes a user-defined callable object, say f, which when called with a vector of numbers is expected to return the value f(x) and the gradient \nabla f(x) at x. DiffBase.GradientResult is a natural choice for the result type, and it should be up to the user to decide whether to use forward or reverse AD, or something completely different.

The question: ForwardDiff.gradient! and similar operate by overwriting the same GradientResult structure. But I don’t want results of different calls to f to share any structure, because that would lead to hideous bugs. The way I see it, I have two interface choices:

  1. f(x) returns results that may share structure, the consumer copys it. Advantage: even if the user messes up, I am safe.
  2. f(x) is expected to return non-shared values, taking care of it itself.

Is there a reason one of these fits better into Julia than the other? I am leaning toward (1), since the overhead of copy is the least of my concerns ATM. Or are there other options?


#2

I may not know much about art …

“the consumer copies it”

  • best not to rest the success of your good work rest on that being true

#3

If you don’t want the returned values to share state, just return values that don’t share state (which seems to be option 2)?


#4

Certainly, but f is supplied by the user, and it is tricky (or nearly impossible?) to test for a structure not being shared. Sharing structure can be a simple user error, and lead to bugs.

BTW, having benchmarked both solutions, the cost of copy seems to be below measurement error.


#5

I have been thinking about a mechanism that copies data by default, but allows functions to signal that the data does not share structure. Something like

struct Unshared{T}
    value::T
end

ensure_unshared(x::Unshared) = x

@generated function ensure_unshared(x)
    :(Unshared($(isbits(x) ? :x : :(deepcopy(x)))))
end

which could be used as eg

struct MyType
    x
    MyType(x::Unshared) = new(x.value)
end

MyType(x) = MyType(ensure_unshared(x))

Does this make sense?