# Abusing \`convert\` as an alternative to \`Union{Nothing, Int64}\`

**URL:** <https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534>\
**Category:** Performance\
**Tags:** union\
**Created:** [August 11, 2025, 6:00pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534 "2025-08-11T18:00:56Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![nhz2](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhz2/32/44428_2.png) [@nhz2](https://discourse.julialang.org/u/nhz2)\
**Post date:** [August 11, 2025, 6:00pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/1 "2025-08-11T18:00:56Z")

</div>

In C it is common to have functions that return a positive integer result on success, or a negative integer if an error happens. When I use these functions, I tend to forget the possible error values and do integer arithmetic or memory management with them, creating harder-to-debug errors.

In Julia, in addition to throwing an error, functions can also return `Union{Nothing, Int64}` for this. But this doesn’t take advantage of the fact that no valid returns are negative at the interface level.

In [[ChunkCodecCore] BREAKING change the return type to `MaybeSize` by nhz2 · Pull Request #72 · JuliaIO/ChunkCodecs.jl · GitHub](https://github.com/JuliaIO/ChunkCodecs.jl/pull/72/files#diff-a9522861b552e0cb6e2464f85e07cea364a1cd1c3fb4517d735f0ebac5791850)  
I am trying to use the `convert` function in base to get a safer version of the C pattern.

```julia
struct MaybeSize
    val::Int64
end
const NOT_SIZE = MaybeSize(typemin(Int64))
function is_size(x::MaybeSize)::Bool
    !signbit(x.val)
end
function Base.Int64(x::MaybeSize)
    if !is_size(x)
        throw(InexactError(:Int64, Int64, x))
    else
        x.val
    end
end
function Base.convert(::Type{Int64}, x::MaybeSize)
    Int64(x)
end
function Base.convert(::Type{MaybeSize}, x::Int64)
    if signbit(x)
        throw(InexactError(:convert, MaybeSize, x))
    else
        MaybeSize(x)
    end
end
function Base.convert(::Type{MaybeSize}, x::Nothing)
    NOT_SIZE
end
function Base.convert(::Type{Nothing}, x::MaybeSize)
    if x !== NOT_SIZE
        throw(InexactError(:convert, Nothing, x))
    else
        nothing
    end
end

```

The wrapping and unwrapping are then mostly automatic as long as the function and caller have explicit type annotations.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [August 11, 2025, 6:30pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/2 "2025-08-11T18:30:02Z")

</div>

> [@nhz2](#):
>
> ```julia-auto
> function Base.convert(::Type{MaybeSize}, x::Int64)
> if signbit(x)
> throw(InexactError(:convert, MaybeSize, x))
> else
> MaybeSize(x)
> end
> end
> 
> ```

Why does the `convert` method include error handling, but the constructor does not? IMO it might be nicer to have the `convert` method just forward to the constructor, and use a separate function for unchecked construction?

> [@nhz2](#):
>
> ```julia-auto
> function Base.convert(::Type{MaybeSize}, x::Nothing)
> NOT_SIZE
> end
> function Base.convert(::Type{Nothing}, x::MaybeSize)
> if x !== NOT_SIZE
> throw(InexactError(:convert, Nothing, x))
> else
> nothing
> end
> end
> 
> ```

IMO ideally you would introduce a new singleton type here, instead of abusing `Nothing`. The convention is for `Nothing` to lack specific handling, I’d say.

---

<div class="post-metadata">

**Author:** ![nhz2](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhz2/32/44428_2.png) [@nhz2](https://discourse.julialang.org/u/nhz2)\
**Post date:** [August 11, 2025, 9:07pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/3 "2025-08-11T21:07:32Z")

</div>

Allowing conversion from `nothing` means that functions that don’t return anything automatically return `NOT_SIZE`, for example:

```julia-repl
julia> function foo()::MaybeSize
       end
foo (generic function with 1 method)

julia> foo()
MaybeSize(-9223372036854775808)

```

But you a right that it would be clearer to require an explicit `return NOT_SIZE`

`MaybeSize` is a plain struct, so I think the default constructor is fine.

---

<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:** [August 12, 2025, 5:50am UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/4 "2025-08-12T05:50:18Z")

</div>

You have discovered the “validation” part of the essay [Parse, don’t validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) 🙂 There’s a followup of that, expanding on the first: [Names are not type safety](https://lexi-lambda.github.io/blog/2020/11/01/names-are-not-type-safety/).

In Julia, we sadly can’t go all the way with type safety since constructors are trivially bypassable (among other reasons), but nonetheless, putting invariant checks in constructors helps a lot for centralizing this kind of check.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [August 12, 2025, 11:21am UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/5 "2025-08-12T11:21:12Z")

</div>

> [@Sukera](#):
>
> You have discovered the “validation” part of the essay [Parse, don’t validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) 🙂 There’s a followup of that, expanding on the first: [Names are not type safety](https://lexi-lambda.github.io/blog/2020/11/01/names-are-not-type-safety/).

I don’t really think validation is an accurate description of what the OP is doing here. They’re just saying that they have a C library that returns negative integers to denote errors, and they want to convert those error values into julia runtime errors.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [August 12, 2025, 11:27am UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/6 "2025-08-12T11:27:45Z")

</div>

> [@nsajko](#):
>
> Why does the `convert` method include error handling, but the constructor does not? IMO it might be nicer to have the `convert` method just forward to the constructor, and use a separate function for unchecked construction?

👍

> [@nsajko](#):
>
> IMO ideally you would introduce a new singleton type here, instead of abusing `Nothing`. The convention is for `Nothing` to lack specific handling, I’d say.

I’m not sure I agree this is abuse of `Nothing`. What is wrong with adding a convert method here?

---

<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:** [August 12, 2025, 11:43am UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/7 "2025-08-12T11:43:46Z")

</div>

> [@Mason](#):
>
> They’re just saying that they have a C library that returns negative integers to denote errors, and they want to convert those error values into julia runtime errors.

Exactly! What I wanted to point out is that by wrapping without immediately throwing, this construction means that you always have to manually check for a potential errors down the line when you use the `MaybeSize` object, even after you’ve already checked (i.e. _validated_) that it is in fact not an error. This muddles the source of the issue (the value returned from the C library), because you generally have no clue where `convert` or something like that is called next.

Barring shenanigans circumventing the constructor and by throwing in the constructor on an error, you know with pretty much 100% certainty that a `MaybeSize` is not a maybe at all, but a guaranteed valid thing when you go on to use it, thereby preventing indexing problems or what have you. In essence, this is what it means _to parse_ and not to validate: encoding correctness in the types of your things.

---

<div class="post-metadata">

**Author:** ![nhz2](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhz2/32/44428_2.png) [@nhz2](https://discourse.julialang.org/u/nhz2)\
**Post date:** [August 12, 2025, 3:28pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/8 "2025-08-12T15:28:08Z")

</div>

Its a bit more subtle than that, because some of those errors are recoverable, for example if inplace decoding fails because the `dst` was too small it can be retried with a larger `dst`.

---

<div class="post-metadata">

**Author:** ![nhz2](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhz2/32/44428_2.png) [@nhz2](https://discourse.julialang.org/u/nhz2)\
**Post date:** [August 12, 2025, 3:42pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/9 "2025-08-12T15:42:07Z")

</div>

Yes [Parse, don’t validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) inspired a lot of the design of the interface, especially on the encoding side, where most errors can be [pre-checked during construction of the encoder](https://github.com/JuliaIO/ChunkCodecs.jl/blob/87f7447d5793b14947b98acddc0305a3b385d723/Bitshuffle/src/compress.jl#L50-L91), before it sees any data.

On the decoding side, there are a huge range of errors that can happen, because in general decoding is like running an interpreter on a mini programming language.  
For decoding the “safe” parsing function is `decode`, it can only return valid data, or error.  
All the other lower level decoding functions need to be used with extreme care.

---

<div class="post-metadata">

**Author:** ![nhz2](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhz2/32/44428_2.png) [@nhz2](https://discourse.julialang.org/u/nhz2)\
**Post date:** [August 12, 2025, 3:58pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/10 "2025-08-12T15:58:54Z")

</div>

Yes, this seems like a downside to `MaybeSize` compared to `Union{Nothing, Int64}`. JET and the compiler seem to be pretty good at understanding these small `Union`s.  
Also, particular implementation might only return an `Int64`, and then the compiler and JET might be able to DCE any `isnothing` branches in the caller. I guess this is, in theory, possible with `MaybeSize`, but it would require the compiler to infer things about the relationships between the possible ranges of values returned.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [August 12, 2025, 5:51pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/11 "2025-08-12T17:51:35Z")

</div>

> [@Mason](#):
>
> > [@nsajko](#):
> >
> > IMO ideally you would introduce a new singleton type here, instead of abusing `Nothing`. The convention is for `Nothing` to lack specific handling, I’d say.
> 
> I’m not sure I agree this is abuse of `Nothing`. What is wrong with adding a convert method here?

It attaches a different meaning (“punning”) to `Nothing` than the one I think is intended by `Base`, making conversion succeed instead of throwing with “cannot convert a value to nothing for assignment”. Admittedly, the documentation around `Nothing` is lacking, however my interpretation is that `Nothing` is not supposed to get any specific handling in the functions defined by `Base` Julia apart from the few methods already defined. Specifically, `nothing` is not supposed to be convertible to other (non-`Nothing`) types. This way is more safe, as unintended assignment etc. fails early.

Apart from the `Missing`-specific handling, this is the entire `Nothing`-specific conversion logic:

> <https://github.com/JuliaLang/julia/blob/7bbb213719b0dd03be1f0d1454a6c299f1e6a3de/base/some.jl#L19-L37>

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [August 12, 2025, 6:02pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/12 "2025-08-12T18:02:35Z")

</div>

I dont really see how this is a pun. Its just saying that there is a meaningful way to convert the failure flag to a `nothing` value and vice versa, but no way to convert a regular value to/from `nothing`

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [August 15, 2025, 1:17pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/13 "2025-08-15T13:17:54Z")

</div>

> [@Mason](#):
>
> Its just saying that there is a meaningful way to convert the failure flag to a `nothing` value and vice versa

Well, yeah. That’s the punning. As far as I understand, the semantics of `Nothing` include `nothing` not being convertible to/from other types. Thus this “meaningful way” introduces a new, different meaning for the same type (`Nothing`), which is what we call punning.

---

<div class="post-metadata">

**Author:** ![nhz2](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhz2/32/44428_2.png) [@nhz2](https://discourse.julialang.org/u/nhz2)\
**Post date:** [August 15, 2025, 3:50pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/14 "2025-08-15T15:50:09Z")

</div>

This got me to think more about what the invariants on the return are.

For `try_decode` it has:

```julia-auto
if is_size(ret)
  @assert Int64(ret) ∈ 0:length(dst)`
end

```

But I could remove this invariant by returning `ret ∈ 0:length(dst)` if the decoding was successful, otherwise return `ret > length(dst)` if the decoding failed due to not enough space in `dst`.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [August 15, 2025, 4:32pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/15 "2025-08-15T16:32:33Z")

</div>

Ah I see what you mean. Yeah, that’s fair.

---

<div class="post-metadata">

**Author:** ![nhz2](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhz2/32/44428_2.png) [@nhz2](https://discourse.julialang.org/u/nhz2)\
**Post date:** [August 19, 2025, 8:14pm UTC](https://discourse.julialang.org/t/abusing-convert-as-an-alternative-to-union-nothing-int64/131534/16 "2025-08-19T20:14:52Z")

</div>

The new zig `Io` interface has a `Limit` type, which seems similar.

> **[Zig Documentation](https://ziglang.org/documentation/master/std/#std.Io.Limit)**
