# On type annotations

**URL:** https://discourse.julialang.org/t/on-type-annotations/116305
**Category:** New to Julia
**Created:** [June 27, 2024, 1:13pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305 "2024-06-27T13:13:24Z")
**Posts on this page:** 20
**Page:** 2

<div class="post-metadata">

### Author: ![mihalybaci](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mihalybaci/32/13528_2.png) [@mihalybaci](https://discourse.julialang.org/u/mihalybaci)
#### Post date: [June 27, 2024, 3:24pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/21 "2024-06-27T15:24:47Z")

</div>

> [@lmiq](#):
>
> I just realized that it is not redudant:

But maybe it is if the output type is specified as well as in the OP?

```julia
julia> function g()::Vector{Float64}
         y = [1.0, 2.0]
         y = [1, 2]
         y
       end
g (generic function with 1 method)

julia> g()
2-element Vector{Float64}:
 1.0
 2.0

julia> function f()::Vector{Float64}
         y::Vector{Float64} = [1.0, 2.0]
         y = [1, 2]
         y
       end
f (generic function with 1 method)

julia> f()
2-element Vector{Float64}:
 1.0
 2.0

```

---

<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: [June 27, 2024, 3:47pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/22 "2024-06-27T15:47:36Z")

</div>

I wouldn’t use a type annotation on extra collected keyword arguments. For example:

```julia
foo(;kwargs...)::Bool = :foo in keys(kwargs)

```

I’m not sure if it is even possible to add a type annotation for `kwargs` here.

---

<div class="post-metadata">

### Author: ![Matthijs\_1971](https://avatars.discourse-cdn.com/v4/letter/m/bb73d2/32.png) [@Matthijs\_1971](https://discourse.julialang.org/u/Matthijs_1971)
#### Post date: [June 27, 2024, 4:05pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/23 "2024-06-27T16:05:41Z")

</div>

There is no denying that sometimes in simple functions the annotation in the function signature is repeated in a variable annotation. This happens in any function that does not all another if a variable is returned. Then the added value of annotation is only that the type is fixated.

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [June 27, 2024, 4:25pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/24 "2024-06-27T16:25:53Z")

</div>

> [@mihalybaci](#):
>
> ```julia
> julia> function f()::Vector{Float64}
> y::Vector{Float64} = [1.0, 2.0]
> y = [1, 2]
> y
> end
> f (generic function with 1 method)
> 
> julia> f()
> 2-element Vector{Float64}:
> 1.0
> 2.0
> 
> ```

No, that’s only because `[1, 2]` is being converted to `Vector{Float64}` on the assignment to `y`. Try with `['a', 'b']`.

---

<div class="post-metadata">

### Author: ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)
#### Post date: [June 27, 2024, 4:30pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/25 "2024-06-27T16:30:40Z")

</div>

I just want to point out that

```julia
x = foo()::T

```

and

```julia
x::T = foo()

```

are different and you should be aware of this.

The first one simply checks that the output of `foo()` is a `T` before assigning it to `x`.

The second one performs a conversion before assigning to `x` and is equivalent to

```julia
x = convert(T, foo())::T

```

Similarly

```julia
function bar()::T 
    return foo()
end

```

and

```julia
function bar()
    return foo()::T 
end

```

are different. The first one inserts a `convert` and is equivalent to

```julia
function bar()
    return convert(T, foo())::T 
end

```

Edit: I’m pointing this out because those extra non-explicit converts can be a performance footgun. If they are part of a style guide, anyone following the guide should be aware of this.

---

<div class="post-metadata">

### Author: ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)
#### Post date: [June 27, 2024, 4:34pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/26 "2024-06-27T16:34:16Z")

</div>

One aspect I would like to highlight here is the difference between making a type assertion at the function definition versus the return statement.

- Asserting at the function definition will `convert`, and maybe throw an error if _conversion_ fails.
- Asserting at the return will `throw` a `TypeError`.

```julia-repl
julia> foo(x)::Int = x
foo (generic function with 1 method)

julia> bar(x) = x::Int
bar (generic function with 1 method)

julia> foo(5)
5

julia> bar(5)
5

julia> foo(5.0)
5

julia> bar(5.0)
ERROR: TypeError: in typeassert, expected Int64, got a value of type Float64

julia> foo(5.5)
ERROR: InexactError: Int64(5.5)

julia> bar(5.5)
ERROR: TypeError: in typeassert, expected Int64, got a value of type Float64

```

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [June 27, 2024, 5:03pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/27 "2024-06-27T17:03:02Z")

</div>

> [@Matthijs\_1971](#):
>
> We intend to use static code checking techniques to make sure that functions are variables are type annotated, so there will be no escaping of annotations for developers.

Do you plan to allow any polymorphism? i.e. any abstract types?

If you want concrete static types for everything, I’m curious — why are you using a dynamic language like Julia? Is it because of some packages/features you want to use, or because you want to do prototyping with dynamic code and then add static annotations for production, or?

No wrong answers here, I’m just wondering what the appeal of Julia is vs. a statically typed language (C++, Rust, …) in this kind of context.

---

<div class="post-metadata">

### Author: ![Matthijs\_1971](https://avatars.discourse-cdn.com/v4/letter/m/bb73d2/32.png) [@Matthijs\_1971](https://discourse.julialang.org/u/Matthijs_1971)
#### Post date: [June 28, 2024, 7:35am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/28 "2024-06-28T07:35:20Z")

</div>

```julia
julia> f(x::Vector{Float64}) = x
f (generic function with 1 method)

julia> x::Vector{Float64} = rand(3);

julia> y::Vector{Float64} = @view x[1:2]
2-element view(::Vector{Float64}, 1:2) with eltype Float64:
 0.24973421955571018
 0.29731016911412356

julia> f(y)
2-element Vector{Float64}:
 0.24973421955571018
 0.29731016911412356

julia>

```

The performance advantage of using a `view` on the caller side is lost, but the workaround is simple.

---

<div class="post-metadata">

### Author: ![Matthijs\_1971](https://avatars.discourse-cdn.com/v4/letter/m/bb73d2/32.png) [@Matthijs\_1971](https://discourse.julialang.org/u/Matthijs_1971)
#### Post date: [June 28, 2024, 8:20am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/29 "2024-06-28T08:20:08Z")

</div>

> The second one performs a conversion before assigning to `x` and is equivalent \> to
> 
> ```julia
> x = convert(T, foo())::T
> 
> ```

Perhaps you meant this?

```julia
x::T = convert(T, foo())

```

Afaik, the return value of `convert` is already of type `T` and it does not need to be asserted. The difference is important for the discussion as `x::T` guarantees that `x` will not change type until it goes end of scope.

Indeed, `convert` may be called. It is only called though when there _is_ something to convert. A function annotated with `::S` returning a local variable of type `S` will not induce a call to `convert`. So the performance hit is absent in this case. We expect that developers will not introduce a lot of `convert` methods for other cases.

```julia
julia> import Base.convert

julia> struct S
                  mem::Int64
              end

julia> function convert(::Type{S}, x::Any)::S
                  println("convert(...): Started.")
                  return S(x)
              end
convert (generic function with 196 methods)

julia> function return_right_type(s::S)::S
                  return S(s.mem * s.mem)
              end
return_right_type (generic function with 1 method)

julia> u::S = return_right_type(S(42))
S(1764)

julia> v::S = 5
convert(...): Started.
5

julia> 

```

---

<div class="post-metadata">

### Author: ![Matthijs\_1971](https://avatars.discourse-cdn.com/v4/letter/m/bb73d2/32.png) [@Matthijs\_1971](https://discourse.julialang.org/u/Matthijs_1971)
#### Post date: [June 28, 2024, 8:44am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/30 "2024-06-28T08:44:46Z")

</div>

Julia was selected for performance first and secondly, engineers with relatively little programming experience can start prototyping in Julia. What we are seeing now is that the code base is becoming harder and harder to maintain as our internal Julia community grows. Type annotations typically are performance neutral and improve understandability of the code - especially when there are many, many structs.

---

<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: [June 28, 2024, 8:50am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/31 "2024-06-28T08:50:31Z")

</div>

> [@Matthijs\_1971](#):
>
> Afaik, the return value of `convert` is already of type `T` and it does not need to be asserted.

Only in some cases. Relevant issue:

> <https://github.com/JuliaLang/julia/issues/42372>
>
> (This is a Julia 2.0 proposal, as it is breaking. It is surely not novel, but I …don't see another such issue open.) We currently treat outer constructors and \`convert\` method definitions as any other generic function, but there's an argument to be made that they should be special-cased by requiring that they always return an object of the stated type. Motivations:
> 
> 1. At present, it's essentially a bug to write fallback methods like this:
> \`\`\`julia
> foo(x::Int) = 1
> foo(x) = foo(Int(x))
> \`\`\`
> Why? Because someone might define
> \`\`\`julia
> struct Foo end
> Int(f::Foo) = f
> \`\`\`
> in which case you get
> \`\`\`julia
> julia\> foo(Foo())
> ERROR: StackOverflowError:
> Stacktrace:
> \[1\] foo(x::Foo) (repeats 79984 times)
> @ Main ./REPL\[3\]:1
> \`\`\`
> and StackOverflowErrors are to be avoided: they can take forever to resolve in some cases, they can at least in principle trash your session, etc.
> 
> The only good way to write such a fallback method is
> \`\`\`julia
> foo(x) = foo(Int(x)::Int)
> \`\`\`
> but I will bet that the \*vast\* majority of developers do not know this or bother with it, and probably everyone finds it ugly and annoying.
> 
> Some might complain about \[idempotent operators\](https://en.wikipedia.org/wiki/Idempotence) and \[involutions\](https://en.wikipedia.org/wiki/Involution\_(mathematics)), for example one might be tempted to define
> \`\`\`julia
> struct Not
> x
> end
> Not(x::Not) = x.x # just strip the \`Not\` wrapper rather than returning \`Not(Not(5))\`
> \`\`\`
> But a solution is to separately introduce \`not\` from \`Not\`, and allow this behavior for \`not\` but not \`Not\` (\`Not\` must \*always\* wrap in another \`Not\` layer).
> 
> 2. In addition to surprising the developer, failing to enforce this is much harder on inference and the compiler, sometimes with dramatically increased risk of invalidation. Consider the case of https://github.com/julia-vscode/CSTParser.jl/issues/308. For the implementation in https://github.com/julia-vscode/CSTParser.jl/blob/778212a77d2977b94a531240877eacff00700111/src/conversion.jl#L133-L203 it is impossible to predict the outcome. For certain package combinations with the SciML ecosystem, this constructor alone is responsible for thousands of invalidations. Such problems can only be detected by devoted and sophisticated sleuths, but designing the language to be bullet-proof against this solves it neatly with very little cost (when you want to return something different, just check \*before\* you call the constructor, not after).

```julia-repl
julia> struct S end

julia> Base.convert(::Type{S}, ::Any) = 3

julia> convert(S, 7)
3

```

---

<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: [June 28, 2024, 9:05am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/32 "2024-06-28T09:05:56Z")

</div>

> [@Matthijs\_1971](#):
>
> To be explicit, we have in mind to type annotate functions, function arguments and variables like so:
> 
> ```julia
> function f(value::Float64, obj::MyStruct)::OtherStruct
> vec::Vector{Float64} = [1.0, 2.0, 3.0]
> other::OtherStruct = calculate_other_struct(value, obj, vec)
> return other
> end
> 
> ```
> 
> If the type annotation is hard-coded as the inferred type, we can’t imagine there is any problem. Is there something we miss?

As possibly the biggest fan of type assertions among the users here: 🤮

Why would you annotate the variable as `Vector`, let alone `Vector{Float64}`? This almost surely serves no purpose and is actively harmful.

Type annotations can be good. They’re basically machine checked internal documentation, for checking _necessary_ invariants. Also they may help type inference in some cases.

However in your case, as others have already indicated, the type annotations are pointless and harmful.

NB: in case this wasn’t mentioned already, a type annotation on a variable doesn’t just type assert, it also calls `convert`. Almost always (or always, if you’re making a style guide…) you should prefer to put the type annotation on the RHS.

To make my criticism more constructive, I’d propose a style guide like so:

1. **Never** put a type annotation on the method return type. Do this in method body instead.
2. **Never** put a type annotation on a variable. Put type assertions on expressions instead, e.g. on the RHS of an assignment.
3. Beginners often overuse type annotations for method arguments. The primary purposes (hope I didn’t miss any) of type annotations for method arguments are:
  1. Defining API
  2. For performance, in limited cases, such as with `@nospecialize` or to force specialization. Consult the Performance tips page in the Manual.
  3. A `MethodError` in dispatch is preferable to an exception being thrown later in the method body, so do use type annotations if this just helps you get an error earlier

---

<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: [June 28, 2024, 9:20am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/33 "2024-06-28T09:20:32Z")

</div>

This recent feature request on the Julia Github repo is relevant to Julia style (guides):

> <https://github.com/JuliaLang/julia/issues/54903>
>
> We've had a few discussions the past few weeks about a feature tentatively dubbe…d \`pragma strict\` after similar constructs in other languages. However, there wasn't really a cohesive writeup of the intent, so triage asked me to write one up to serve as the basis for discussion and fleshing out. I intend to edit this issue as the idea evolves.
> 
> \## Basic idea
> 
> The basic idea of the \`pragma strict\` feature is to have an opt-in mechanism of turning julia programs that are semantically valid, but undesirable for other reasons (e.g. using ambiguous syntax that should have arguably been disallowed, but we can't for backwards compatibility reasons) into errors. This would be an opt-in feature for developers who have personal, organizational or regulatory requirements for requiring stricter coding standards. An additional motivation is to provide an additional vehicle for low-frictition language evolution. For example, if a specific opt-in turns out to be popular across the majority of packages, a potential julia 2.0 that made the opt-in automatic while technically breaking, would be largely non-breaking in practice.
> 
> We are not imagining a single \`strict mode\` opt in here, but rather a finer grained set of options, plus versioned collections of options for particular use cases. See the last section for a an initial list of such options.
> 
> It is worth emphasizing again that this feature is only intended to disallow undesirable programs that are otherwise semantically valid. It is not intended to cause meaningful semantic differences in programs that are valid both in standard semantics and under the opt-in restrictions (i.e. turning on the restrictions may cause things to error, but if they don't the program should behave the same).
> 
> \## How does the opt-in work?
> 
> One of the primary questions in this proposal is how the user expresses the opt-in. There's a few separate semantic options, each
> with a number of potential syntax options.
> 
> 1. Per module opt-in like our existing \`Experimental.@compiler\_options\`
> 2. Per file opt-in (e.g. using a magic comment on the first line) - popular in some other languages
> 3. Per project opt-in in Project.toml
> 
> After some discussion on triage, a Project.toml-level opt-in seems like the best option. The primary motivation here is to allow opt-ins that need to be done in the parser (e.g. whitespace requirements). We don't currently define the execution ordering of parsing and execution for packages, so a module-toplevel opt-in may be semantically too late (relatedly, it may be ambiguous what happens when the opt-in is placed in the middle of a disallowed parse). An additional concern is that ideally IDE tooling would be able to understand the active set of restrictions without having to look at the code.
> 
> \## Concrete Project.toml syntax options
> 
> One convenient option would be reusing Preferences.jl. One might imagine a julia-level preference like:
> 
> \`\`\`
> name = "MyPackage"
> 
> \[preferences.julia\]
> strict = \["nomultiassign", "uniqueidentifiers"\]
> \`\`\`
> 
> This doesn't fully mesh with the usual preferences semantics, since preferences are ordinarily uniqued per-UUID while
> they would be private for a particular package, but this might be ok. Alternatively, we could reserve the \`strict\` key
> in each individual package's preference table:
> 
> \`\`\`
> name = "MyPackage"
> 
> \[preferences.MyPackage\]
> strict = \["nomultiassign", "nolocalshadow", "noglobalshadow"\]
> \`\`\`
> 
> Alternatively, we could have a new top-level \`strict\` section:
> \`\`\`
> name = "MyPackage"
> \[strict\]
> julia = \["nomultiassign", "nolocalshadow", "noglobalshadow"\]
> \`\`\`
> \## Initial idea list for opt-in options
> 
> In this section, I'm collecting a list of potential options that might be implemented. However, I am not at this point asking people to brainstorm all the possibilities that could be implemented. I'm also not asking for detailed discussion on what should or should not be included in a particular option. Rather, I wanted to have a place to list all the ideas that have already come
> up and a place to link any issues that could be addressed by this feature. Full design discussions for individual flags can be had on the PRs to implement them once the overall mechanism is in place.
> 
> \### Individual options
> 
> \- \`nomultiassign\`
> 	
> Disallows multiple assignments in the same expression without parantheses. I.e. disallows \`a = b, c = d, e, = f = (1, 2)\`
> 
> \- \`nolocalshadow\`
> 
> Disallows shadowing of local variables, e.g. in the following
> 
> \`\`\`
> 
> function foo()
> 
> for i = 1:10
> 
> for i = 1:10 # Error shadowing local \`i\` 
> 
> end
> 
> all(1:10) do i # Error shadowing local \`i\`
> iszero(i)
> end
> end
> end
> \`\`\`
> 
> \- \`noglobalshadow\`
> 
> Disallows shadowing of global variables, e.g. in the following:
> \`\`\`
> function foo()
> missing = false # Error local \`missing\` shadows imported global \`missing\`
> end
> \`\`\`
> 
> \- Some variant of unique assignment
> 
> Stefan had proposed introducing a unique assignment operator, e.g. \`:=\` for which there would then be a corresponding opt-in to enforce all assignments use it
> 
> \- Enforce export versioning
> 
> If we implement some variant of export versioning, there could be an opt-in forbidding unversioned exports.
> 
> \### Collections
> 
> The idea of collections is that users in general don't want to individually decide which opt ins matter to them, but will likely be following a standard set by their organizations or prescribed by a style guide. To this end, there could be meta opt-ins like "basestyle", which would
> activate a standard collection of opt-ins. These collections should be versioned and activated based on the min-compat version of Julia. In this way, new opt-ins can be added to a collection, without automatically activating them on a julia version upgrade.

Keep an eye on the implementation status, and possibly adopt suggestions from the issue comments into the style guide as you see fit.

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [June 28, 2024, 9:55am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/34 "2024-06-28T09:55:01Z")

</div>

> [@Matthijs\_1971](#):
>
> The performance advantage of using a `view` on the caller side is lost, but the workaround is simple.

There you a just converting the view into a newly allocated vector. That’s just a workaround for the limitation of the interface, and defeats the purpose of the view.

In parallel: you mention large structs and long functions. The former can and should be strictly type annotated, nobody disagrees. The later should be avoided, they are a maintenance nightmare with or without strict types.

---

<div class="post-metadata">

### Author: ![mihalybaci](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mihalybaci/32/13528_2.png) [@mihalybaci](https://discourse.julialang.org/u/mihalybaci)
#### Post date: [June 28, 2024, 12:20pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/35 "2024-06-28T12:20:39Z")

</div>

Yeah, but in the case of `Int`s both end up getting converted so there is seemingly little benefit to using both. As for strings

```julia
julia> function f()
         y::Vector{Float64} = [1.0, 2.0]
         y = ["a","b"] # errors here
         y
       end
f (generic function with 1 method)

julia> f()
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Float64

julia> function g()::Vector{Float64}
         y = [1.0, 2.0]
         y = ["a","b"]
         y # errors here
       end
g (generic function with 1 method)

julia> g()
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Float64

```

One errors on the internal reassignment, and one errors on conversion before returning, but in both cases future type instabilities are prevented by only allowing return values of `Vector{Float64}`. Also, both versions are flagged by `@code_warntype` as having `y::Union{Vector{Float64}, Vector{Int64}}`. Again, where this happens is differs, but the core result – having a `Union` type – is the same.

That’s really what I meant by redundant, not that they are exactly identical in practice, but that they both provide the same level of type stability with regards to the output.

---

<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: [June 28, 2024, 2:36pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/36 "2024-06-28T14:36:47Z")

</div>

> [@mihalybaci](#):
>
> both versions are flagged by `@code_warntype` as having `y::Union{Vector{Float64}, Vector{Int64}}`

As an aside, `@code_warntype` is not always entirely truthful. But here, it is true that the _name_ `y` can have either of those types across the entire life of the function. However, here there is no ambiguity as to what it is at any particular time. And the `@code_warntype` does clearly show that it knows the return value is a `Vector{String}`.

A variable is not a place in memory, so a variable that takes multiple types does not need to be able to store and interchange those types arbitrarily. It is simply a name (“binding”) that you can use to refer to some datum. Just because I can put a surfboard in my car for one trip and a snowboard in my car for another doesn’t mean I can’t know which it holds at any given time.

```julia-repl
julia> @code_typed g()
CodeInfo(
1 ─ %1 = Core.tuple("a", "b")::Tuple{String, String}
│ %2 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{String}, svec(Any, Int64), 0, :(:ccall), Vector{String}, 2, 2))::Vector{String}
└── goto #7 if not true
2 ┄ %4 = φ (#1 => 1, #6 => %13)::Int64
│ %5 = φ (#1 => 1, #6 => %14)::Int64
│ %6 = Base.getfield(%1, %4, false)::String
│ Base.arrayset(false, %2, %6, %4)::Vector{String}
│ %8 = (%5 === 2)::Bool
└── goto #4 if not %8
3 ─ goto #5
4 ─ %11 = Base.add_int(%5, 1)::Int64
└── goto #5
5 ┄ %13 = φ (#4 => %11)::Int64
│ %14 = φ (#4 => %11)::Int64
│ %15 = φ (#3 => true, #4 => false)::Bool
│ %16 = Base.not_int(%15)::Bool
└── goto #7 if not %16
6 ─ goto #2
7 ┄ goto #8
8 ─ return %2
) => Vector{String}

```

Not only is there no ambiguity, `Float64` never even appears here because it’s part of dead code that gets eliminated.

* * *

It’s hard to imagine that no one at your company will ever want to call the same code with different types. What about a function that finds the smallest positive value in a collection? Do you really want people to write multiple versions of that function that are explicitly annoted to operate on each of `Vector{Float64}`, `Vector{Int64}`, `SVector{3,Float64}`, and `SubArray{Float64, 1, Matrix{Float64}, Tuple{UnitRange{Int64}, Int64}, true}`? And what if they recognize a bug and fix it in most of those, but accidentally miss one where it persists for goodness-knows how long? That’s where maintainability really starts to crumble. There’s even [a principle for this](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) in software engineering. This proposal is not entirely maintainability up-side.

A few strategically-placed comments at confusing places are much more informative than exhaustive (and exhausting) annotation. Types are usually the least interesting part of code anyway – what/why/how the code does things is the part that’s actually important to communicate.

* * *

As commenters indicated above, a RHS type annotation is actually a _stronger_ assertion than one on the LHS, because a LHS annotation will attempt to `convert` the value and only throw if that fails. The RHS annotation will fail without recourse if it is mislabeled. So requiring all RHS to be annotated would result in stricter typing requirements than LHS. Besides, it sounds like your proposal is to require annotations on every LHS so it’s not like it’s fewer annotations than RHS would be.

```julia-repl
julia> x1::Int = 3.0
3.0

julia> x2 = 3.0::Int
ERROR: TypeError: in typeassert, expected Int64, got a value of type Float64

```

* * *

If you are too onerous to your users and fail to provide aggressive supervision, they can simply satisfy the letter (but not spirit) of your requirements by annotating every variable `::Any`

```julia
function h()
  y::Any = 4.0
  return y
end

```

And yet, despite the `::Any` (or a `::Real`) annotation, the `@code_warntype` is not fooled and knows `y` is specifically a `Float64`. These `::Any` annotations are harmless and effectless except for adding work for the parser and compiler.

---

<div class="post-metadata">

### Author: ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)
#### Post date: [June 28, 2024, 2:46pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/37 "2024-06-28T14:46:14Z")

</div>

> [@mikmoore](#):
>
> A few strategically-placed comments at confusing places are much more informative than exhaustive (and exhausting) annotation.

More like anno- **y** -tation, amirite?

(Sorry I’ll see myself out)

---

<div class="post-metadata">

### Author: ![Jorge\_Vieyra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jorge_vieyra/32/6527_2.png) [@Jorge\_Vieyra](https://discourse.julialang.org/u/Jorge_Vieyra)
#### Post date: [June 30, 2024, 11:02am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/38 "2024-06-30T11:02:49Z")

</div>

Julia is not C.

---

<div class="post-metadata">

### Author: ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)
#### Post date: [June 30, 2024, 12:23pm UTC](https://discourse.julialang.org/t/on-type-annotations/116305/39 "2024-06-30T12:23:44Z")

</div>

> [@Matthijs\_1971](#):
>
> To be explicit, we have in mind to type annotate functions, function arguments and variables like so:
> 
> ```julia
> function f(value::Float64, obj::MyStruct)::OtherStruct
> vec::Vector{Float64} = [1.0, 2.0, 3.0]
> other::OtherStruct = calculate_other_struct(value, obj, vec)
> return other
> end
> 
> ```
> 
> If the type annotation is hard-coded as the inferred type, we can’t imagine there is any problem. Is there something we miss?

While one should be able to code like this and we should provide conveniences to support this, I am not sure if I would recommend this as part of company-wide style guide.

As many have noted above, these are not merely annotations but also have consequences that could be harmful in several ways including negatively impacting performance, reducing reusability, and throwing errors you did not intend to throw.

In particular, I recommend focusing more on right side assertions than left side assertions. Left hand side assertions can result in implicit conversions as noted above. While implicit conversions can be useful, they can occur at long distances from where the type annotation was created. This really should be done with intention and not due to style. Right hand side type assertions do not involve conversion but merely make the statement that a variable or return value should be of a certain type. That’s usually what I want and is stricter. If conversions need to happen, they should probably be explicitly done and their results should be asserted as well.

I also recommend the use of type parameters in this case. They would help to increase reusability but also help show how types are related.

```julia
function f(value::T, obj::MyStruct) where T
    vec = T[1.0, 2.0, 3.0]
    other = calculate_other_struct(value::T, obj::MyStruct, vec::Vector{T})::OtherStruct
    return other::OtherStruct
end

```

For some functions in very specific contexts, this may be appropriate. In a general context, over annotating can result in less flexible and more fragile code. Type assertions invite potential unintended _runtime_ errors even when the unasserted code would have run perfectly fine. In many cases, you really only want such errors to occur only during analysis and not runtime.

Where I think type assertions would be most useful are return statements. This helps to enforce the interface of a function and makes other type assertions unnecessary.

---

<div class="post-metadata">

### Author: ![Matthijs\_1971](https://avatars.discourse-cdn.com/v4/letter/m/bb73d2/32.png) [@Matthijs\_1971](https://discourse.julialang.org/u/Matthijs_1971)
#### Post date: [July 1, 2024, 10:36am UTC](https://discourse.julialang.org/t/on-type-annotations/116305/40 "2024-07-01T10:36:04Z")

</div>

> [@mkitti](#):
>
> While implicit conversions can be useful, they can occur at long distances from where the type annotation was created.

Can you clarify this? If any function is type annotated, the conversion happens upon return.

Also, I fail to see how performance is negatively impacted. If there is nothing to convert, `convert` is not called, see [above](https://discourse.julialang.org/t/on-type-annotations/116305/29). The proposal is to add the annotations that would otherwise be inferred. A very big advantage of annotation of variables is that the type can not change any more for the lifetime of the variable.

[Previous page](https://discourse.julialang.org/t/on-type-annotations/116305.md?page=1)

[Next page](https://discourse.julialang.org/t/on-type-annotations/116305.md?page=3)
