├── .github ├── dependabot.yml └── workflows │ ├── CompatHelper.yml │ ├── Documentation.yml │ └── SpellCheck.yml ├── .typos.toml ├── LICENSE ├── Project.toml ├── README.md ├── docs ├── Project.toml ├── make.jl └── src │ └── assets │ ├── favicon.ico │ └── logo.png ├── src └── SciMLStyle.jl └── test └── runtests.jl /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" # Location of package manifests 6 | schedule: 7 | interval: "weekly" 8 | ignore: 9 | - dependency-name: "crate-ci/typos" 10 | update-types: ["version-update:semver-patch", "version-update:semver-minor"] 11 | -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | name: CompatHelper 2 | on: 3 | schedule: 4 | - cron: 0 0 * * * 5 | workflow_dispatch: 6 | jobs: 7 | CompatHelper: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Pkg.add("CompatHelper") 11 | run: julia -e 'using Pkg; Pkg.add("CompatHelper")' 12 | - name: CompatHelper.main() 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} 16 | run: julia -e 'using CompatHelper; CompatHelper.main(;subdirs=["docs"])' 17 | -------------------------------------------------------------------------------- /.github/workflows/Documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'release-' 8 | tags: '*' 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: julia-actions/setup-julia@latest 17 | with: 18 | version: '1' 19 | - name: Install dependencies 20 | run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' 21 | - name: Build and deploy 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token 24 | DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key 25 | run: julia --project=docs/ --code-coverage=user docs/make.jl 26 | - uses: julia-actions/julia-processcoverage@v1 27 | - uses: codecov/codecov-action@v5 28 | with: 29 | files: lcov.info 30 | -------------------------------------------------------------------------------- /.github/workflows/SpellCheck.yml: -------------------------------------------------------------------------------- 1 | name: Spell Check 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | typos-check: 7 | name: Spell Check with Typos 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout Actions Repository 11 | uses: actions/checkout@v4 12 | - name: Check spelling 13 | uses: crate-ci/typos@v1.18.1 -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-words] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SciML Open Source Scientific Machine Learning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "SciMLStyle" 2 | uuid = "59b18918-51b4-4202-aa6b-1b76b310ffb6" 3 | authors = ["Chris Rackauckas and contributors"] 4 | version = "0.1.0" 5 | 6 | [deps] 7 | 8 | [compat] 9 | julia = "1.6" 10 | 11 | [extras] 12 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 13 | 14 | [targets] 15 | test = ["Test"] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SciML Style Guide for Julia 2 | 3 | [![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) 4 | [![Global Docs](https://img.shields.io/badge/docs-SciML-blue.svg)](https://docs.sciml.ai/SciMLStyle/stable/) 5 | 6 | The SciML Style Guide is a style guide for the Julia programming language. It is used by the 7 | [SciML Open Source Scientific Machine Learning Organization](https://sciml.ai/). It covers proper 8 | styles to allow for easily high-quality, readable, robust, safety, and fast code that is easy to 9 | maintain for production and deployment. 10 | 11 | It is open to discussion with the community. Please file an issue or open a PR to discuss changes to 12 | the style guide. 13 | 14 | **Table of Contents** 15 | - [SciML Style Guide for Julia](#sciml-style-guide-for-julia) 16 | - [Code Style Badge](#code-style-badge) 17 | - [Overarching Dogmas of the SciML Style](#overarching-dogmas-of-the-sciml-style) 18 | - [Consistency vs Adherence](#consistency-vs-adherence) 19 | - [Community Contribution Guidelines](#community-contribution-guidelines) 20 | - [Open source contributions are allowed to start small and grow over time](#open-source-contributions-are-allowed-to-start-small-and-grow-over-time) 21 | - [Generic code is preferred unless code is known to be specific](#generic-code-is-preferred-unless-code-is-known-to-be-specific) 22 | - [Internal types should match the types used by users when possible](#internal-types-should-match-the-types-used-by-users-when-possible) 23 | - [Trait definition and adherence to generic interface is preferred when possible](#trait-definition-and-adherence-to-generic-interface-is-preferred-when-possible) 24 | - [Macros should be limited and only be used for syntactic sugar](#macros-should-be-limited-and-only-be-used-for-syntactic-sugar) 25 | - [Errors should be caught as high as possible, and error messages should be contextualized for newcomers](#errors-should-be-caught-as-high-as-possible-and-error-messages-should-be-contextualized-for-newcomers) 26 | - [Subpackaging and interface packages is preferred over conditional modules via Requires.jl](#subpackaging-and-interface-packages-is-preferred-over-conditional-modules-via-requiresjl) 27 | - [Functions should either attempt to be non-allocating and reuse caches, or treat inputs as immutable](#functions-should-either-attempt-to-be-non-allocating-and-reuse-caches-or-treat-inputs-as-immutable) 28 | - [Out-Of-Place and Immutability is preferred when sufficient performant](#out-of-place-and-immutability-is-preferred-when-sufficient-performant) 29 | - [Tests should attempt to cover a wide gamut of input types](#tests-should-attempt-to-cover-a-wide-gamut-of-input-types) 30 | - [When in doubt, a submodule should become a subpackage or separate package](#when-in-doubt-a-submodule-should-become-a-subpackage-or-separate-package) 31 | - [Globals should be avoided whenever possible](#globals-should-be-avoided-whenever-possible) 32 | - [Type-stable and Type-grounded code is preferred wherever possible](#type-stable-and-type-grounded-code-is-preferred-wherever-possible) 33 | - [Closures should be avoided whenever possible](#closures-should-be-avoided-whenever-possible) 34 | - [Numerical functionality should use the appropriate generic numerical interfaces](#numerical-functionality-should-use-the-appropriate-generic-numerical-interfaces) 35 | - [Functions should capture one underlying principle](#functions-should-capture-one-underlying-principle) 36 | - [Internal choices should be exposed as options whenever possible](#internal-choices-should-be-exposed-as-options-whenever-possible) 37 | - [Prefer code reuse over rewrites whenever possible](#prefer-code-reuse-over-rewrites-whenever-possible) 38 | - [Prefer to not shadow functions](#prefer-to-not-shadow-functions) 39 | - [Avoid unmaintained dependencies](#avoid-unmaintained-dependencies) 40 | - [Avoid unsafe operations](#avoid-unsafe-operations) 41 | - [Avoid non public operations in Julia Base and packages](#avoid-non-public-operations-in-julia-base-and-packages) 42 | - [Always default to constructs which initialize data](#always-default-to-constructs-which-initialize-data) 43 | - [Use extra precaution when running external processes](#use-extra-precaution-when-running-external-processes) 44 | - [Avoid eval whenever possible](#avoid-eval-whenever-possible) 45 | - [Avoid bounds check removal, and if done, add appropriate manual checks](#avoid-bounds-check-removal-and-if-done-add-appropriate-manual-checks) 46 | - [Avoid ccall unless necessary, and use safe ccall practices when required](#avoid-ccall-unless-necessary-and-use-safe-ccall-practices-when-required) 47 | - [Validate all user inputs to avoid code injection](#validate-all-user-inputs-to-avoid-code-injection) 48 | - [Ensure secure random number generators are used when required](#ensure-secure-random-number-generators-are-used-when-required) 49 | - [Be aware of distributed computing encryption principles](#be-aware-of-distributed-computing-encryption-principles) 50 | - [Always immediately flush secret data after handling](#always-immediately-flush-secret-data-after-handling) 51 | - [Specific Rules](#specific-rules) 52 | - [High Level Rules](#high-level-rules) 53 | - [General Naming Principles](#general-naming-principles) 54 | - [Comments](#comments) 55 | - [Modules](#modules) 56 | - [Functions](#functions) 57 | - [Function Argument Precedence](#function-argument-precedence) 58 | - [Tests and Continuous Integration](#tests-and-continuous-integration) 59 | - [Whitespace](#whitespace) 60 | - [NamedTuples](#namedtuples) 61 | - [Numbers](#numbers) 62 | - [Ternary Operator](#ternary-operator) 63 | - [For loops](#for-loops) 64 | - [Function Type Annotations](#function-type-annotations) 65 | - [Struct Type Annotations](#struct-type-annotations) 66 | - [Macros](#macros) 67 | - [Types and Type Annotations](#types-and-type-annotations) 68 | - [Package version specifications](#package-version-specifications) 69 | - [Documentation](#documentation) 70 | - [Error Handling](#error-handling) 71 | - [Arrays](#arrays) 72 | - [Line Endings](#line-endings) 73 | - [VS-Code Settings](#vs-code-settings) 74 | - [JuliaFormatter](#juliaformatter) 75 | - [References](#references) 76 | 77 | 78 | ## Code Style Badge 79 | 80 | Let contributors know your project is following the SciML Style Guide by adding the badge to your `README.md`. 81 | 82 | ```md 83 | [![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) 84 | ``` 85 | 86 | ## Overarching Dogmas of the SciML Style 87 | 88 | ### Consistency vs Adherence 89 | 90 | According to PEP8: 91 | 92 | > A style guide is about consistency. Consistency with this style guide is important. 93 | > Consistency within a project is more important. Consistency within one module or function is the most important. 94 | 95 | > But most importantly: know when to be inconsistent -- sometimes the style guide just doesn't apply. 96 | > When in doubt, use your best judgment. Look at other examples and decide what looks best. And don't hesitate to ask! 97 | 98 | Some code within the SciML organization is old, on life support, donated by researchers to be maintained. 99 | Consistency is the number one goal, so updating to match the style guide should happen on a repo-by-repo 100 | basis, i.e. do not update one file to match the style guide (leaving all other files behind). 101 | 102 | ### Community Contribution Guidelines 103 | 104 | For a comprehensive set of community contribution guidelines, refer to [ColPrac](https://github.com/SciML/ColPrac). 105 | A relevant point to highlight PRs should do one thing. In the context of style, this means that PRs that update 106 | the style of a package's code should not be mixed with fundamental code contributions. This separation makes it 107 | easier to ensure that large style improvements are isolated from substantive (and potentially breaking) code changes. 108 | 109 | ### Open source contributions are allowed to start small and grow over time 110 | 111 | If the standard for code contributions is that every PR needs to support every possible input type that anyone can 112 | think of, the barrier would be too high for newcomers. Instead, the principle is to be as correct as possible to 113 | begin with, and grow the generic support over time. All recommended functionality should be tested, and any known 114 | generality issues should be documented in an issue (and with a `@test_broken` test when possible). However, a 115 | function that is known to not be GPU-compatible is not grounds to block merging, rather it is encouraged for a 116 | follow-up PR to improve the general type support! 117 | 118 | ### Generic code is preferred unless code is known to be specific 119 | 120 | For example, the code: 121 | 122 | ```julia 123 | function f(A, B) 124 | for i in 1:length(A) 125 | A[i] = A[i] + B[i] 126 | end 127 | end 128 | ``` 129 | 130 | would not be preferred for two reasons. One is that it assumes `A` uses one-based indexing, which would fail in cases 131 | like [OffsetArrays](https://github.com/JuliaArrays/OffsetArrays.jl) and [FFTViews](https://github.com/JuliaArrays/FFTViews.jl). 132 | Another issue is that it requires indexing, while not all array types support indexing (for example, 133 | [CuArrays](https://github.com/JuliaGPU/CuArrays.jl)). A more generic and compatible implementation of this function would be 134 | to use broadcast, for example: 135 | 136 | ```julia 137 | function f(A, B) 138 | @. A = A + B 139 | end 140 | ``` 141 | 142 | which would allow support for a wider variety of array types. 143 | 144 | ### Internal types should match the types used by users when possible 145 | 146 | If `f(A)` takes the input of some collections and computes an output from those collections, then it should be 147 | expected that if the user gives `A` as an `Array`, the computation should be done via `Array`s. If `A` was a 148 | `CuArray`, then it should be expected that the computation should be internally done using a `CuArray` (or appropriately 149 | error if not supported). For these reasons, constructing arrays via generic methods, like `similar(A)`, is preferred when 150 | writing `f` instead of using non-generic constructors like `Array(undef,size(A))` unless the function is documented as 151 | being non-generic. 152 | 153 | ### Trait definition and adherence to generic interface is preferred when possible 154 | 155 | Julia provides many different interfaces, for example: 156 | 157 | - [Iteration](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-iteration) 158 | - [Indexing](https://docs.julialang.org/en/v1/manual/interfaces/#Indexing) 159 | - [Broadcast](https://docs.julialang.org/en/v1/manual/interfaces/#man-interfaces-broadcasting) 160 | 161 | Those interfaces should be followed when possible. For example, when defining broadcast overloads, 162 | one should implement a `BroadcastStyle` as suggested by the documentation instead of simply attempting 163 | to bypass the broadcast system via `copyto!` overloads. 164 | 165 | When interface functions are missing, these should be added to Base Julia or an interface package, 166 | like [ArrayInterface.jl](https://github.com/JuliaArrays/ArrayInterface.jl). Such traits should be 167 | declared and used when appropriate. For example, if a line of code requires mutation, the trait 168 | `ArrayInterface.ismutable(A)` should be checked before attempting to mutate, and informative error 169 | messages should be written to capture the immutable case (or, an alternative code that does not 170 | mutate should be given). 171 | 172 | One example of this principle is demonstrated in the generation of Jacobian matrices. In many scientific 173 | applications, one may wish to generate a Jacobian cache from the user's input `u0`. A naive way to generate 174 | this Jacobian is `J = similar(u0,length(u0),length(u0))`. However, this will generate a Jacobian `J` such 175 | that `J isa Matrix`. 176 | 177 | ### Macros should be limited and only be used for syntactic sugar 178 | 179 | Macros define new syntax, and for this reason, they tend to be less composable than other coding styles 180 | and require prior familiarity to be easily understood. One principle to keep in mind is, "can the person 181 | reading the code easily picture what code is being generated?". For example, a user of Soss.jl may not know 182 | what code is being generated by: 183 | 184 | ```julia 185 | @model (x, α) begin 186 | σ ~ Exponential() 187 | β ~ Normal() 188 | y ~ For(x) do xj 189 | Normal(α + β * xj, σ) 190 | end 191 | return y 192 | end 193 | ``` 194 | 195 | and thus using such a macro as the interface is not preferred when possible. However, a macro like 196 | [`@muladd`](https://github.com/SciML/MuladdMacro.jl) is trivial to picture on a code (it recursively 197 | transforms `a*b + c` to `muladd(a,b,c)` for more 198 | [accuracy and efficiency](https://en.wikipedia.org/wiki/Multiply%E2%80%93accumulate_operation)), so using 199 | such a macro, for example: 200 | 201 | ```julia 202 | julia> @macroexpand(@muladd k3 = f(t + c3 * dt, @. uprev + dt * (a031 * k1 + a032 * k2))) 203 | :(k3 = f((muladd)(c3, dt, t), (muladd).(dt, (muladd).(a032, k2, (*).(a031, k1)), uprev))) 204 | ``` 205 | 206 | is recommended. Some macros in this category are: 207 | 208 | - `@inbounds` 209 | - [`@muladd`](https://github.com/SciML/MuladdMacro.jl) 210 | - `@view` 211 | - [`@named`](https://github.com/SciML/ModelingToolkit.jl) 212 | - `@.` 213 | - [`@..`](https://github.com/YingboMa/FastBroadcast.jl) 214 | 215 | Some performance macros, like `@simd`, `@threads`, or 216 | [`@turbo` from LoopVectorization.jl](https://github.com/JuliaSIMD/LoopVectorization.jl), 217 | make an exception in that their generated code may be foreign to many users. However, they still are 218 | classified as appropriate uses as they are syntactic sugar since they do (or should) not change the behavior 219 | of the program in measurable ways other than performance. 220 | 221 | ### Errors should be caught as high as possible, and error messages should be contextualized for newcomers 222 | 223 | Whenever possible, defensive programming should be used to check for potential errors before they are encountered 224 | deeper within a package. For example, if one knows that `f(u0,p)` will error unless `u0` is the size of `p`, this 225 | should be caught at the start of the function to throw a domain specific error, for example "parameters and initial 226 | condition should be the same size". 227 | 228 | This contextualization should result in error messages that use terminology related to the user facing API (vs. 229 | referencing internal implementation details). Ideally, such error messages should not only describe the 230 | issue in language that will be familiar to the user but also include suggestions, where possible, of how 231 | to correct the issue. 232 | 233 | ### Subpackaging and interface packages is preferred over conditional modules via Requires.jl 234 | 235 | Requires.jl should be avoided at all costs. If an interface package exists, such as 236 | [ChainRulesCore.jl](https://github.com/JuliaDiff/ChainRulesCore.jl) for defining automatic differentiation 237 | rules without requiring a dependency on the whole ChainRules.jl system, or 238 | [RecipesBase.jl](https://github.com/JuliaPlots/RecipesBase.jl) which allows for defining Plots.jl 239 | plot recipes without a dependency on Plots.jl, a direct dependency on these interface packages is 240 | preferred. 241 | 242 | Otherwise, instead of resorting to a conditional dependency using Requires.jl, it is 243 | preferred to create subpackages, i.e. smaller independent packages kept within the same Github repository 244 | with independent versioning and package management. An example of this is seen in 245 | [Optimization.jl](https://github.com/SciML/Optimization.jl) which has subpackages like 246 | [OptimizationBBO.jl](https://github.com/SciML/Optimization.jl/tree/master/lib/OptimizationBBO) for 247 | BlackBoxOptim.jl support. 248 | 249 | Some important interface packages to know about are: 250 | 251 | - [ChainRulesCore.jl](https://github.com/JuliaDiff/ChainRulesCore.jl) 252 | - [RecipesBase.jl](https://github.com/JuliaPlots/RecipesBase.jl) 253 | - [ArrayInterface.jl](https://github.com/JuliaArrays/ArrayInterface.jl) 254 | - [CommonSolve.jl](https://github.com/SciML/CommonSolve.jl) 255 | - [SciMLBase.jl](https://github.com/SciML/SciMLBase.jl) 256 | 257 | ### Functions should either attempt to be non-allocating and reuse caches, or treat inputs as immutable 258 | 259 | Mutating codes and non-mutating codes fall into different worlds. When a code is fully immutable, 260 | the compiler can better reason about dependencies, optimize the code, and check for correctness. 261 | However, many times a code that makes the fullest use of mutation can outperform even what the best compilers 262 | of today can generate. That said, the worst of all worlds is when code mixes mutation with non-mutating 263 | code. Not only is this a mishmash of coding styles, but it also has the potential non-locality and compiler 264 | proof issues of mutating code while not fully benefiting from the mutation. 265 | 266 | ### Out-Of-Place and Immutability is preferred when sufficient performant 267 | 268 | Mutation is used to get more performance by decreasing the number of heap allocations. However, 269 | if it's not helpful for heap allocations in a given spot, do not use mutation. Mutation is scary 270 | and should be avoided unless it gives an immediate benefit. For example, if 271 | matrices are sufficiently large, then `A*B` is as fast as `mul!(C,A,B)`, and thus writing 272 | `A*B` is preferred (unless the rest of the function is being careful about being fully non-allocating, 273 | in which case this should be `mul!` for consistency). 274 | 275 | Similarly, when defining types, using `struct` is preferred to `mutable struct` unless mutating 276 | the struct is a common occurrence. Even if mutating the struct is a common occurrence, see whether 277 | using [Setfield.jl](https://github.com/jw3126/Setfield.jl) is sufficient. The compiler will optimize 278 | the construction of immutable structs, and thus this can be more efficient if it's not too much of a 279 | code hassle. 280 | 281 | ### Tests should attempt to cover a wide gamut of input types 282 | 283 | Code coverage numbers are meaningless if one does not consider the input types. For example, one can 284 | hit all the code with `Array`, but that does not test whether `CuArray` is compatible! Thus it's 285 | always good to think of coverage not in terms of lines of code but in terms of type coverage. A good 286 | list of number types to think about are: 287 | 288 | - `Float64` 289 | - `Float32` 290 | - `Complex` 291 | - [`Dual`](https://github.com/JuliaDiff/ForwardDiff.jl) 292 | - `BigFloat` 293 | 294 | Array types to think about testing are: 295 | 296 | - `Array` 297 | - [`OffsetArray`](https://github.com/JuliaArrays/OffsetArrays.jl) 298 | - [`CuArray`](https://github.com/JuliaGPU/CUDA.jl) 299 | 300 | ### When in doubt, a submodule should become a subpackage or separate package 301 | 302 | Keep packages focused on one core idea. If there's something separate enough to be a submodule, could it 303 | instead be a separate, well-tested and documented package to be used by other packages? Most likely 304 | yes. 305 | 306 | ### Globals should be avoided whenever possible 307 | 308 | Global variables should be avoided whenever possible. When required, global variables should be 309 | constants and have an all uppercase name separated with underscores (e.g. `MY_CONSTANT`). They should be 310 | defined at the top of the file, immediately after imports and exports but before an `__init__` function. 311 | If you truly want mutable global style behavior you may want to look into mutable containers. 312 | 313 | ### Type-stable and Type-grounded code is preferred wherever possible 314 | 315 | Type-stable and type-grounded code helps the compiler create not only more optimized code, but also 316 | faster to compile code. Always keep containers well-typed, functions specializing on the appropriate 317 | arguments, and types concrete. 318 | 319 | ### Closures should be avoided whenever possible 320 | 321 | Closures can cause accidental type instabilities that are difficult to track down and debug; in the 322 | long run, it saves time to always program defensively and avoid writing closures in the first place, 323 | even when a particular closure would not have been problematic. A similar argument applies to reading 324 | code with closures; if someone is looking for type instabilities, this is faster to do when code does 325 | not contain closures. 326 | Furthermore, if you want to update variables in an outer scope, do so explicitly with `Ref`s or self 327 | defined structs. 328 | For example, 329 | ```julia 330 | map(Base.Fix2(getindex, i), vector_of_vectors) 331 | ``` 332 | is preferred over 333 | ```julia 334 | map(v -> v[i], vector_of_vectors) 335 | ``` 336 | or 337 | ```julia 338 | [v[i] for v in vector_of_vectors] 339 | ``` 340 | 341 | ### Numerical functionality should use the appropriate generic numerical interfaces 342 | 343 | While you can use `A\b` to do a linear solve inside a package, that does not mean that you should. 344 | This interface is only sufficient for performing factorizations, and so that limits the scaling 345 | choices, the types of `A` that can be supported, etc. Instead, linear solves within packages should 346 | use LinearSolve.jl. Similarly, nonlinear solves should use NonlinearSolve.jl. Optimization should use 347 | Optimization.jl. Etc. This allows the full generic choice to be given to the user without depending 348 | on every solver package (effectively recreating the generic interfaces within each package). 349 | 350 | ### Functions should capture one underlying principle 351 | 352 | Functions mean one thing. Every dispatch of `+` should be "the meaning of addition on these types". 353 | While in theory you could add dispatches to `+` that mean something different, that will fail in 354 | generic code for which `+` means addition. Thus, for generic code to work, code needs to adhere to 355 | one meaning for each function. Every dispatch should be an instantiation of that meaning. 356 | 357 | ### Internal choices should be exposed as options whenever possible 358 | 359 | Whenever possible, numerical values and choices within scripts should be exposed as options 360 | to the user. This promotes code reusability beyond the few cases the author may have expected. 361 | 362 | ### Prefer code reuse over rewrites whenever possible 363 | 364 | If a package has a function you need, use the package. Add a dependency if you need to. If the 365 | function is missing a feature, prefer to add that feature to said package and then add it as a 366 | dependency. If the dependency is potentially troublesome, for example because it has a high 367 | load time, prefer to spend time helping said package fix these issues and add the dependency. 368 | Only when it does not seem possible to make the package "good enough" should using the package 369 | be abandoned. If it is abandoned, consider building a new package for this functionality as you 370 | need it, and then make it a dependency. 371 | 372 | ### Prefer to not shadow functions 373 | 374 | Two functions can have the same name in Julia by having different namespaces. For example, 375 | `X.f` and `Y.f` can be two different functions, with different dispatches, but the same name. 376 | This should be avoided whenever possible. Instead of creating `MyPackage.sort`, consider 377 | adding dispatches to `Base.sort` for your types if these new dispatches match the underlying 378 | principle of the function. If it doesn't, prefer to use a different name. While using `MyPackage.sort` 379 | is not conflicting, it is going to be confusing for most people unfamiliar with your code, 380 | so `MyPackage.special_sort` would be more helpful to newcomers reading the code. 381 | 382 | ### Avoid unmaintained dependencies 383 | 384 | Packages should only be depended on if they have maintainers who are responsive. Good code requires 385 | good communities. If maintainers do not respond to breakage within 2 weeks with multiple notices, 386 | then all dependencies from that organization should be considered for removal. Note that some issues 387 | may take a long time to fix, so it may take more time than 2 weeks to fix, it's simply that the 388 | communication should be open, consistent, and timely. 389 | 390 | ### Avoid unsafe operations 391 | 392 | Like other high-level languages that provide strong safety guarantees by default, Julia nevertheless has a small 393 | set of operations that bypass normal checks. These operations are clearly marked with the prefix unsafe_. By using an 394 | “unsafe” operation, the programmer asserts that they know the operation is valid even though the language cannot 395 | automatically ensure it. For high reliability these constructs should be avoided or carefully inspected during code 396 | review. They are: 397 | 398 | * unsafe_load 399 | * unsafe_store! 400 | * unsafe_read 401 | * unsafe_write 402 | * unsafe_string 403 | * unsafe_wrap 404 | * unsafe_convert 405 | * unsafe_copyto! 406 | * unsafe_pointer_to_objref 407 | * ccall 408 | * @ccall 409 | * @inbounds 410 | 411 | ### Avoid non public operations in Julia Base and packages 412 | 413 | The Julia standard library and packages developed in the Julia programming language have an intended public API indicated by 414 | marking symbols with the export keyword or [in v1.11+ with the new `public` keyword](https://github.com/JuliaLang/julia/pull/50105). 415 | However, it is possible to use non public names via explicit qualification, e.g. Base.foobar. This practice is not necessarily unsafe, 416 | but should be avoided since non public operations may have unexpected invariants and behaviors, and are subject to changes in future 417 | releases of the language. 418 | 419 | Note that qualified names are commonly used in method definitions to clarify that a function is being extended, e.g. function Base.getindex(...) … end. 420 | Such uses do not fall under this concern. 421 | 422 | ### Always default to constructs which initialize data 423 | 424 | For certain newly-allocated data structures, such as numeric arrays, the Julia compiler and runtime do not check whether data is accessed before it has been 425 | initialized. Therefore such data structures can “leak” information from one part of a program to another. Uninitialized structures should be avoided in favor 426 | of functions like `zeros` and `fill` that create data with well-defined contents. If code does allocate uninitialized memory, it should ensure that this memory 427 | is fully initialized before being returned from the function in which it is allocated. 428 | 429 | Constructs which create uninitialized memory should only be used if there is a demonstrated performance impact and it should ensure that all memory is initialized 430 | in the same function in which the array is intended to be used. 431 | 432 | Example: 433 | 434 | ```julia 435 | function make_array(n::Int) 436 | A = Vector{Int}(undef, n) 437 | # function body 438 | return A 439 | end 440 | ``` 441 | 442 | This function allocates an integer array with undefined initial contents (note the language forces you to request this explicitly). 443 | A code reviewer should ensure that the function body assigns every element of the array. 444 | One can similarly create structs with undefined fields, and if used this way, one should ensure all fields are initialized: 445 | 446 | ```julia 447 | struct Foo 448 | x::Int 449 | Foo() = new() 450 | end 451 | 452 | julia> Foo().x 453 | 139736495677280 454 | ``` 455 | 456 | ### Use extra precaution when running external processes 457 | 458 | The Julia standard library contains a `run` function and other facilities for running external processes. Any program that does this 459 | is only as safe as the external process it runs. If this cannot be avoided, then best practices for using these features are: 460 | 461 | 1. Only run fixed, known executables, and do not derive the path of the executable to run from user input or other outside sources. 462 | 2. Make sure the executables used have also passed required audit procedures. 463 | 3. Make sure to handle process failure (non-zero exit code). 464 | 4. If possible, run external processes in a sandbox or “jail” environment with access only to what they need in terms of files, file handles and ports. 465 | 466 | When run in a sandbox or jail, external processes can actually improve security since the subprocess is isolated from the rest of the system by the kernel. 467 | 468 | ### Avoid eval whenever possible 469 | 470 | Julia contains an `eval` function that executes program expressions constructed at run time. This is not in itself unsafe, but because the code it will run 471 | is not textually evident in the surrounding program, it can be difficult to determine what it will do. For example, a Julia program could construct and eval 472 | an expression that performs an unsafe operation without the operation being clearly evident to a code reviewer or analysis tool. 473 | 474 | In general, programs should try to avoid using eval in ways that are influenced by user input because there are many subtle ways this can lead to arbitrary code 475 | execution. If user input must influence eval, the input should only be used to select from a known list of possible behaviors. Approaches using pattern matching 476 | to try to validate expressions should be viewed with extreme suspicion because they tend to be brittle and/or exploitable. 477 | 478 | Note: it is common for Julia programs to invoke `eval` or `@eval` at the top level, in order to generate global definitions programmatically. Such uses are generally safe. 479 | 480 | ### Avoid bounds check removal, and if done, add appropriate manual checks 481 | 482 | While Julia checks the bounds of all array operations by default, it is possible to manually disable bounds checks in a program using @inbounds. 483 | Note that in early versions of Julia (pre v1.9) this could be used as a performance optimization, but in later versions it can demonstrably 484 | reduce performance and thus one should never immediately default to bounds check removal as a performance habit. 485 | 486 | Uses of this construct should be carefully audited during code review. For maximum safety, it should be avoided or programs should be run with the 487 | command line option --check-bounds=yes to enable all checks regardless of manual annotations. 488 | 489 | To check a use of @inbounds for correctness, it suffices to examine all array indexing expressions (e.g. a[i]) within the expression it applies to, 490 | and ensure that each index will always be within the bounds of the indexed array. For example the following common use pattern is valid: 491 | 492 | ```julia 493 | @inbounds for i in eachindex(A) 494 | A[i] = i 495 | end 496 | ``` 497 | 498 | By inspection, the variable `i` will always be a valid index for `A`. 499 | 500 | For contrast, the following use is invalid unless A is known to be a specific type (eg: `Vector`) 501 | 502 | ```julia 503 | @inbounds for i in 1:length(A) 504 | A[i] = i 505 | end 506 | ``` 507 | 508 | `@inbounds` should be applied to as narrow a region of code as possible. When applied to a large block of code, it can be difficult to identify 509 | and verify all indexing expressions. 510 | 511 | ### Avoid ccall unless necessary, and use safe ccall practices when required 512 | 513 | Calling C (and Fortran) libraries from Julia is very easy: the ccall syntax (and the more convenient @ccall macro) allow calling C libraries 514 | without any need for glue files or boilerplate. They do require caution, however: the programmer tells Julia what the signature of each library 515 | function is and if this is not done correctly, it can be the cause of crashes and thus security vulnerabilities. An exploit is just a crash that 516 | an attacker has arranged to fail in a worse way than it would have randomly. 517 | 518 | Safe use of ccall depends on both automated and manual measures. 519 | 520 | What Julia does (automated): 521 | 522 | * Julia provides aliases for C types like Cint, Clong, Cchar, Csize_t, etc. It makes sure that these match what the C ABI on the machine that code is running on expects them to be. 523 | * The Clang.jl package automates the process of turning C header files into valid ccall invocations. 524 | * Pkg+BinaryBuilder.jl allows precise versioning of binary dependencies. 525 | * Julia objects passed directly to ccall are protected from garbage collection (GC) for the duration of the call. 526 | 527 | What you must do (manual): 528 | * When writing ccall signatures, programmers should always look at the signature in the C header file and make sure the signature used in Julia matches exactly. 529 | * Use Julia’s C type aliases. For example, if an argument in C is of type int then the corresponding type in Julia is Cint, not Int — on most platforms Int will be the same size as Clong rather than Cint. 530 | * If a raw pointer to memory managed by Julia’s GC is passed to C via ccall, the owning object must be preserved using GC.@preserve around the use of ccall. See the documentation of this macro for more information and examples of proper usage. 531 | 532 | ### Validate all user inputs to avoid code injection 533 | 534 | When writing programs that construct any kind of code based on user input, extra caution is required and the user input must be validated or escaped. 535 | For example, a common type of attack in web applications written in all programming languages is SQL injection: a user input is spliced into an SQL 536 | query to construct a customized query based on the user’s input. If raw user input is spliced into an SQL query as a string, it is easy to craft 537 | inputs that will execute arbitrary SQL commands, including destructive ones or ones that will reveal private data to an attacker. To prevent this, 538 | the user input should be passed as parameters to SQL prepared statements; a package such as SqlStrings.jl can be used to do this without a syntax burden. 539 | This protects against malicious input, but also encourages systematic marshaling of Julia types into SQL types. If string interpolation must be used, 540 | all user input should be either validated to match a strict, safe pattern (e.g. only consists of decimal digits or ASCII letters), or it should be 541 | escaped to ensure that SQL treats it only as data, not as code (e.g. turn a user input into an escaped string literal). 542 | 543 | While we have talked specifically about SQL here, this issue is not limited to SQL. The same concern occurs when executing programs via shells, for example. 544 | Julia is more secure than most programming languages in this respect because the default mechanism for running external code (see Cmd objects in the Julia manual) 545 | is carefully designed to not be susceptible to this kind of injection, but programmers may be tempted to use a shell to call external code for convenience sake. 546 | The fact that a shell must be explicitly invoked in Julia helps catch these kinds of circumstances. Using a shell like this is usually a bad idea and can typically 547 | be avoided. If an external shell must be used, be certain that any user data used to construct the shell command is carefully validated or escaped to avoid shell 548 | injection attacks. 549 | 550 | ## Ensure secure random number generators are used when required 551 | 552 | The default pseudo-random number generator in Julia, which can be accessed by calling `rand()` and `randn()`, for example, is intended for simulation purposes and 553 | not for applications requiring cryptographic security. An attacker can, by observing a series of random values, construct its internal state and predict future pseudo-random values. 554 | For security-sensitive applications like generating secret values used for authentication, the `RandomDevice()` random-number generator should be used. This produces genuinely 555 | random numbers which cannot be predicted. 556 | 557 | ### Be aware of distributed computing encryption principles 558 | 559 | Julia’s distributed computing uses unencrypted TCP/IP sockets for communication by default and expects to be running on a fully trusted cluster. If `using Distributed` in your code through 560 | `@distributed`, `pmap`, etc., be aware that the communication channels are not encrypted. Julia opens ports for communication between processes in a distributed cluster. A pre-generated 561 | random cookie is necessary to successfully connect (Julia 0.5 onwards), which defeats arbitrary external connections. This mechanism is described in detail in https://github.com/JuliaLang/julia/pull/16292. 562 | 563 | For additional security, these communication channels can be encrypted through the use of a custom ClusterManager which enables SSH port forwarding, or uses some other mechanism to encrypt the communication channel. 564 | 565 | ### Always immediately flush secret data after handling 566 | 567 | When it’s necessary to manage secret data (for example, a user’s password) it’s desirable to have this erased from memory immediately after finishing with the data. However, when a normal `String` or `Array` is used as 568 | a container for such data, the underlying bytes persist after the container is deallocated and in principle could be recovered by an attacker at a later time. There are also situations where a string or array may be 569 | implicitly copied, for example, if it is assigned to a location with a compatible but different type, it will be converted, thus creating a copy of the original data. Normally this is harmless and convenient, but 570 | making copies of secrets is obviously bad for security. To prevent this, Julia provides the type `Base.SecretBuffer` and a `shred!` function which should be called immediately after the data is finished with. 571 | The contents of a `SecretBuffer` must be explicitly extracted — it is never implicitly copied — and its contents will be automatically shredded upon garbage collection of the `SecretBuffer` object if the `shred!` 572 | function was never called on it, with a warning indicating that the buffer should have been explicitly shredded by the programmer. 573 | 574 | ## Specific Rules 575 | 576 | ### High Level Rules 577 | 578 | - Use 4 spaces per indentation level, no tabs. 579 | - Try to adhere to a 92 character line length limit. 580 | 581 | ### General Naming Principles 582 | 583 | - All type names should be `CamelCase`. 584 | - All struct names should be `CamelCase`. 585 | - All module names should be `CamelCase`. 586 | - All function names should be `snake_case` (all lowercase). 587 | - All variable names should be `snake_case` (all lowercase). 588 | - All constant names should be `SNAKE_CASE` (all uppercase). 589 | - All abstract type names should begin with `Abstract`. 590 | - All type variable names should be a single capital letter, preferably related to the value being typed. 591 | - Whole words are usually better than abbreviations or single letters. 592 | - Variables meant to be internal or private to a package should be denoted by prepending two underscores, i.e. `__`. 593 | - Single letters can be okay when naming a mathematical entity, i.e. an entity whose purpose or non-mathematical "meaning" is likely only known by downstream callers. For example, `a` and `b` would be appropriate names when implementing `*(a::AbstractMatrix, b::AbstractMatrix)`, since the "meaning" of those arguments (beyond their mathematical meaning as matrices, which is already described by the type) is only known by the caller. 594 | - Unicode is fine within code where it increases legibility, but in no case should Unicode be used in public APIs. 595 | This is to allow support for terminals that cannot use Unicode: if a keyword argument must be η, then it can be 596 | exclusionary to uses on clusters which do not support Unicode inputs. 597 | 598 | ### Comments 599 | 600 | - `TODO` to mark todo comments and `XXX` to mark comments about currently broken code 601 | - Quote code in comments using backticks (e.g. `` `variable_name` ``). 602 | - When possible, code should be changed to incorporate information that would have been in 603 | a comment. For example, instead of commenting `# fx applies the effects to a tree`, simply 604 | change the function and variable names `apply_effects(tree)`. 605 | - Comments referring to Github issues and PRs should add the URL in the comments. 606 | Only use inline comments if they fit within the line length limit. If your comment 607 | cannot be fitted inline, then place the comment above the content to which it refers: 608 | 609 | ```julia 610 | # Yes: 611 | 612 | # Number of nodes to predict. Again, an issue with the workflow order. Should be updated 613 | # after data is fetched. 614 | p = 1 615 | 616 | # No: 617 | 618 | p = 1 # Number of nodes to predict. Again, an issue with the workflow order. Should be 619 | # updated after data is fetched. 620 | ``` 621 | 622 | - In general, comments above a line of code or function are preferred to inline comments. 623 | 624 | ### Modules 625 | 626 | - Module imports should occur at the top of a file or right after a `module` declaration. 627 | - Module imports in packages should either use `import` or explicitly declare the imported functionality, for example 628 | `using Dates: Year, Month, Week, Day, Hour, Minute, Second, Millisecond`. 629 | - Import and using statements should be separated, and should be divided by a blank line. 630 | 631 | ```julia 632 | # Yes: 633 | import A: a 634 | import C 635 | 636 | using B 637 | using D: d 638 | 639 | # No: 640 | import A: a 641 | using B 642 | import C 643 | using D: d 644 | ``` 645 | 646 | - Large sets of imports are preferred to be written in space filling lines separated by commas. 647 | 648 | ```julia 649 | # Yes: 650 | using A, B, C, D 651 | 652 | # No: 653 | using A 654 | using B 655 | using C 656 | using D 657 | 658 | # No: 659 | using A, 660 | B, 661 | C, 662 | D 663 | ``` 664 | 665 | - Exported variables should be considered part of the public API, and changing their interface constitutes a 666 | breaking change. 667 | - Any exported variables should be sufficiently unique. I.e., do not export `f` as that is very likely to clash with 668 | something else. 669 | - A file that includes the definition of a module, should not include any other code that runs outside that module. 670 | i.e. the module should be declared at the top of the file with the `module` keyword and `end` at the bottom of the file. 671 | No other code before, or after (except for module docstring before). 672 | In this case, the code within the module block should **not** be indented. 673 | - Sometimes, e.g. for tests, or for namespacing an enumeration, it *is* desirable to declare a submodule midway through a file. 674 | In this case, the code within the submodule **should** be indented. 675 | 676 | ### Functions 677 | 678 | - Only use short-form function definitions when they fit on a single line: 679 | 680 | ```julia 681 | # Yes: 682 | foo(x::Int64) = abs(x) + 3 683 | 684 | # No: 685 | foobar(array_data::AbstractArray{T}, item::T) where {T <: Int64} = T[ 686 | abs(x) * abs(item) + 3 for x in array_data 687 | ] 688 | ``` 689 | 690 | - Inputs should be required unless a default is historically expected or likely to be applicable to >95% of use cases. 691 | For example, the tolerance of a differential equation solver was set to a default of `abstol=1e-6,reltol=1e-3` as a 692 | generally correct plot in most cases, and is an expectation from back in the 90's. In that case, using the historically 693 | expected and most often useful default tolerances is justified. However, if one implements `GradientDescent`, the learning 694 | rate needs to be adjusted for each application (based on the size of the gradient), and thus a default of 695 | `GradientDescent(learning_rate = 1)` is not recommended. 696 | - Arguments that do not have defaults should preferably be made into positional arguments. The newer syntax of required 697 | keyword arguments can be useful, but should not be abused. Notable exceptions are cases where "either or" arguments are 698 | accepted, for example, if defining `g` or `dgdu` is sufficient, then making them both keyword arguments with `= nothing` 699 | and checking that either is not `nothing` (and throwing an appropriate error) is recommended if distinct dispatches with 700 | different types is not possible. 701 | - When calling a function, always separate your keyword arguments from your positional arguments with a semicolon. 702 | This avoids mistakes in ambiguous cases (such as splatting a Dict). 703 | - When writing a function that sends a lot of keyword arguments to another function, say sending keyword arguments to a 704 | differential equation solver, use a named tuple keyword argument instead of splatting the keyword arguments. For example, 705 | use `diffeq_solver_kwargs = (; abstol=1e-6, reltol=1e-6,)` as the API and use `solve(prob, alg; diffeq_solver_kwargs...)` 706 | instead of splatting all keyword arguments. 707 | - Functions that mutate arguments should be appended with `!`. 708 | - [Avoid type piracy](https://docs.julialang.org/en/v1/manual/style-guide/#Avoid-type-piracy). I.e., do not add methods 709 | to functions you don't own on types you don't own. Either own the types or the function. 710 | - Functions should prefer instances instead of types for arguments. For example, for a solver type `Tsit5`, the interface 711 | should use `solve(prob,Tsit5())`, not `solve(prob,Tsit5)`. The reason for this is multifold. For one, passing a type 712 | has different specialization rules, so functionality can be slower unless `::Type{Tsit5}` is written in the dispatches 713 | that use it. Secondly, this allows for default and keyword arguments to extend the choices, which may become useful 714 | for some types down the line. Using this form allows for adding more options in a non-breaking manner. 715 | - If the number of arguments is too large to fit into a 92 character line, then use as many arguments as possible within 716 | a line and start each new row with the same indentation, preferably at the same column as the `(` but this can be moved 717 | left if the function name is very long. For example: 718 | 719 | ```julia 720 | # Yes 721 | function my_large_function(argument1, argument2, 722 | argument3, argument4, 723 | argument5, x, y, z) 724 | 725 | # No 726 | function my_large_function(argument1, 727 | argument2, 728 | argument3, 729 | argument4, 730 | argument5, 731 | x, 732 | y, 733 | z) 734 | ``` 735 | 736 | 737 | ### Function Argument Precedence 738 | 739 | 1. **Function argument**. 740 | Putting a function argument first permits the use of [`do`](https://docs.julialang.org/en/v1/base/base/#do) blocks for passing 741 | multiline anonymous functions. 742 | 743 | 2. **I/O stream**. 744 | Specifying the `IO` object first permits passing the function to functions such as 745 | [`sprint`](https://docs.julialang.org/en/v1/base/io-network/#Base.sprint), e.g. `sprint(show, x)`. 746 | 747 | 3. **Input being mutated**. 748 | For example, in [`fill!(x, v)`](https://docs.julialang.org/en/v1/base/arrays/#Base.fill!), `x` is the object being mutated and it 749 | appears before the value to be inserted into `x`. 750 | 751 | 4. **Type**. 752 | Passing a type typically means that the output will have the given type. 753 | In [`parse(Int, "1")`](https://docs.julialang.org/en/v1/base/numbers/#Base.parse), the type comes before the string to parse. 754 | There are many such examples where the type appears first, but it's useful to note that 755 | in [`read(io, String)`](https://docs.julialang.org/en/v1/base/io-network/#Base.read), the `IO` argument appears before the type, which is 756 | in keeping with the order outlined here. 757 | 758 | 5. **Input not being mutated**. 759 | In `fill!(x, v)`, `v` is *not* being mutated and it comes after `x`. 760 | 761 | 6. **Key**. 762 | For associative collections, this is the key of the key-value pair(s). 763 | For other indexed collections, this is the index. 764 | 765 | 7. **Value**. 766 | For associative collections, this is the value of the key-value pair(s). 767 | In cases like [`fill!(x, v)`](https://docs.julialang.org/en/v1/base/arrays/#Base.fill!), this is `v`. 768 | 769 | 8. **Everything else**. 770 | Any other arguments. 771 | 772 | 9. **Varargs**. 773 | This refers to arguments that can be listed indefinitely at the end of a function call. 774 | For example, in `Matrix{T}(undef, dims)`, the dimensions can be given as a 775 | [`Tuple`](https://docs.julialang.org/en/v1/base/base/#Core.Tuple), e.g. `Matrix{T}(undef, (1,2))`, or as [`Vararg`](https://docs.julialang.org/en/v1/base/base/#Core.Vararg)s, 776 | e.g. `Matrix{T}(undef, 1, 2)`. 777 | 778 | 10. **Keyword arguments**. 779 | In Julia keyword arguments have to come last anyway in function definitions; they're 780 | listed here for the sake of completeness. 781 | 782 | The vast majority of functions will not take every kind of argument listed above; the 783 | numbers merely denote the precedence that should be used for any applicable arguments 784 | to a function. 785 | 786 | ### Tests and Continuous Integration 787 | 788 | - The high level `runtests.jl` file should only be used to shuttle to other test files. 789 | - Every set of tests should be included into a [`@safetestset`](https://github.com/YingboMa/SafeTestsets.jl). 790 | A standard `@testset` does not fully enclose all defined values, such as functions defined in a `@testset`, and 791 | thus can "leak". 792 | - Test includes should be written in one line, for example: 793 | 794 | ```julia 795 | @time @safetestset "Jacobian Tests" include("interface/jacobian_tests.jl") 796 | ``` 797 | 798 | - Every test script should be fully reproducible in isolation. I.e., one should be able to copy paste that script 799 | and receive the results. 800 | - Test scripts should be grouped based on categories, for example tests of the interface vs tests for numerical 801 | convergence. Grouped tests should be kept in the same folder. 802 | - A `GROUP` environment variable should be used to specify test groups for parallel testing in continuous integration. 803 | A fallback group `All` should be used to specify all the tests that should be run when a developer runs `]test Package` 804 | locally. As an example, see the 805 | [OrdinaryDiffEq.jl test structure](https://github.com/SciML/OrdinaryDiffEq.jl/blob/v6.10.0/test/runtests.jl) 806 | - Tests should include downstream tests to major packages which use the functionality, to ensure continued support. 807 | Any update that breaks the downstream tests should follow with a notification to the downstream package of why the 808 | support was broken (preferably in the form of a PR that fixes support), and the package should be given a major 809 | version bump in the next release if the changed functionality was part of the public API. 810 | - CI scripts should use the default settings unless required. 811 | - CI scripts should test the Long-Term Support (LTS) release and the current stable release. Nightly tests are only 812 | necessary for packages with a heavy reliance on specific compiler details. 813 | - Any package supporting GPUs should include continuous integration for GPUs. 814 | - [Doctests](https://juliadocs.github.io/Documenter.jl/stable/man/doctests/) should be enabled except for the examples 815 | that are computationally-prohibitive to have as part of continuous integration. 816 | 817 | ### Whitespace 818 | 819 | - Avoid extraneous whitespace immediately inside parentheses, square brackets or braces. 820 | 821 | ```julia 822 | # Yes: 823 | spam(ham[1], [eggs]) 824 | 825 | # No: 826 | spam( ham[ 1 ], [ eggs ] ) 827 | ``` 828 | 829 | - Avoid extraneous whitespace immediately before a comma or semicolon: 830 | 831 | ```julia 832 | # Yes: 833 | if x == 4 @show(x, y); x, y = y, x end 834 | 835 | # No: 836 | if x == 4 @show(x , y) ; x , y = y , x end 837 | ``` 838 | 839 | - Avoid whitespace around `:` in ranges. Use brackets to clarify expressions on either side. 840 | 841 | ```julia 842 | # Yes: 843 | ham[1:9] 844 | ham[9:-3:0] 845 | ham[1:step:end] 846 | ham[lower:upper-1] 847 | ham[lower:upper - 1] 848 | ham[lower:(upper + offset)] 849 | ham[(lower + offset):(upper + offset)] 850 | 851 | # No: 852 | ham[1: 9] 853 | ham[9 : -3: 1] 854 | ham[lower : upper - 1] 855 | ham[lower + offset:upper + offset] # Avoid as it is easy to read as `ham[lower + (offset:upper) + offset]` 856 | ``` 857 | 858 | - Avoid using more than one space around an assignment (or other) operator to align it with another: 859 | 860 | ```julia 861 | # Yes: 862 | x = 1 863 | y = 2 864 | long_variable = 3 865 | 866 | # No: 867 | x = 1 868 | y = 2 869 | long_variable = 3 870 | ``` 871 | 872 | - Surround most binary operators with a single space on either side: assignment (`=`), [updating operators](https://docs.julialang.org/en/v1/manual/mathematical-operations/#Updating-operators-1) (`+=`, `-=`, etc.), [numeric comparisons operators](https://docs.julialang.org/en/v1/manual/mathematical-operations/#Numeric-Comparisons-1) (`==`, `<`, `>`, `!=`, etc.), [lambda operator](https://docs.julialang.org/en/v1/manual/functions/#man-anonymous-functions-1) (`->`). Binary operators may be excluded from this guideline include: the [range operator](https://docs.julialang.org/en/v1/base/math/#Base.::) (`:`), [rational operator](https://docs.julialang.org/en/v1/base/math/#Base.://) (`//`), [exponentiation operator](https://docs.julialang.org/en/v1/base/math/#Base.:^-Tuple{Number,%20Number}) (`^`), [optional arguments/keywords](https://docs.julialang.org/en/v1/manual/functions/#Optional-Arguments-1) (e.g. `f(x = 1; y = 2)`). 873 | 874 | ```julia 875 | # Yes: 876 | i = j + 1 877 | submitted += 1 878 | x^2 < y 879 | 880 | # No: 881 | i=j+1 882 | submitted +=1 883 | x^2 splicer(1:10, 2) 1136 | 1:2:9 1137 | 1138 | julia> splicer([3.0, 5, 7, 9], 2) 1139 | 2-element Array{Float64,1}: 1140 | 3.0 1141 | 7.0 1142 | ``` 1143 | 1144 | ### Struct Type Annotations 1145 | 1146 | Annotations on type fields need to be given a little more thought, since field access is not concrete unless 1147 | the compiler can infer the type (see [type-dispatch design](https://www.stochasticlifestyle.com/type-dispatch-design-post-object-oriented-programming-julia/) for details). Since well-inferred code is 1148 | preferred, abstract type annotations, i.e. 1149 | 1150 | ```julia 1151 | mutable struct MySubString <: AbstractString 1152 | string::AbstractString 1153 | offset::Integer 1154 | endof::Integer 1155 | end 1156 | ``` 1157 | 1158 | are not recommended. Instead a concretely-typed struct: 1159 | 1160 | ```julia 1161 | mutable struct MySubString <: AbstractString 1162 | string::String 1163 | offset::Int 1164 | endof::Int 1165 | end 1166 | ``` 1167 | 1168 | is preferred. If generality is required, then parametric typing is preferred, i.e.: 1169 | 1170 | ```julia 1171 | mutable struct MySubString{T<:Integer} <: AbstractString 1172 | string::String 1173 | offset::T 1174 | endof::T 1175 | end 1176 | ``` 1177 | 1178 | Untyped fields should be explicitly typed `Any`, i.e.: 1179 | 1180 | ```julia 1181 | struct StructA 1182 | a::Any 1183 | end 1184 | ``` 1185 | 1186 | ### Macros 1187 | 1188 | - Do not add spaces between assignments when there are multiple assignments. 1189 | 1190 | ```julia 1191 | Yes: 1192 | @parameters a = b 1193 | @parameters a=b c=d 1194 | 1195 | No: 1196 | @parameters a = b c = d 1197 | ``` 1198 | 1199 | ### Types and Type Annotations 1200 | 1201 | - Avoid elaborate union types. `Vector{Union{Int,AbstractString,Tuple,Array}}` should probably 1202 | be `Vector{Any}`. This will reduce the amount of extra strain on compilation checking many 1203 | branches. 1204 | - Unions should be kept to two or three types only for branch splitting. Unions of three types 1205 | should be kept to a minimum for compile times. 1206 | - Do not use `===` to compare types. Use `isa` or `<:` instead. 1207 | 1208 | ### Package version specifications 1209 | 1210 | - Use [Semantic Versioning](https://semver.org/) 1211 | - For simplicity, avoid including the default caret specifier when specifying package version requirements. 1212 | 1213 | ```julia 1214 | # Yes: 1215 | DataFrames = "0.17" 1216 | 1217 | # No: 1218 | DataFrames = "^0.17" 1219 | ``` 1220 | 1221 | - For accuracy, do not use constructs like `>=` to avoid upper bounds. 1222 | - Every dependency should have a bound. 1223 | - All packages should use [CompatHelper](https://github.com/JuliaRegistries/CompatHelper.jl) and attempt to 1224 | stay up to date with the dependencies. 1225 | - The lower bound on dependencies should be the last tested version. 1226 | 1227 | ### Documentation 1228 | 1229 | - Documentation should always attempt to be at the highest level possible. I.e., documentation of an interface that 1230 | all methods follow is preferred to documenting every method, and documenting the interface of an abstract type is 1231 | preferred to documenting all the subtypes individually. All instances should then refer to the higher level 1232 | documentation. 1233 | - Documentation should use [Documenter.jl](https://juliadocs.github.io/Documenter.jl/stable/). 1234 | - Tutorials should come before reference materials. 1235 | - Every package should have a starting tutorial that covers "the 90% use case", i.e. the ways that most people will 1236 | want to use the package. 1237 | - The tutorial should show a complete workflow and be opinionated about said workflow. For example, when writing a tutorial 1238 | about a simulator, pick a plotting package and show how to plot it. 1239 | - Variable names in tutorials are important. If you use `u0`, then all other codes will copy that naming scheme. 1240 | Show potential users the right way to use your code with the right naming. 1241 | - When applicable, tutorials on how to use the "high performance advanced features" should be separated from the beginning tutorial. 1242 | - All documentation should summarize the contents before going into specifics of API docstrings. 1243 | - Most modules, types and functions should have [docstrings](http://docs.julialang.org/en/v1/manual/documentation/). 1244 | - Prefer documenting accessor functions instead of fields when possible. Documented fields are part of the public API 1245 | and changing their contents/name constitutes a breaking change. 1246 | - Only exported functions are required to be documented. 1247 | - Avoid documenting commonly overloaded methods, such as `==`. 1248 | - Try to document a function and not individual methods where possible, as typically all methods will have similar docstrings. 1249 | - If you are adding a method to a function that already has a docstring only add a docstring if the 1250 | behavior of your function deviates from the existing docstring. 1251 | - Docstrings are written in [Markdown](https://en.wikipedia.org/wiki/Markdown) and should be concise. 1252 | - Docstring lines should be wrapped at 92 characters. 1253 | 1254 | ```julia 1255 | """ 1256 | bar(x[, y]) 1257 | 1258 | Compute the Bar index between `x` and `y`. If `y` is missing, compute the Bar index between 1259 | all pairs of columns of `x`. 1260 | """ 1261 | function bar(x, y) ... 1262 | ``` 1263 | 1264 | - It is recommended that you have a blank line between the headings and the content when the content is of sufficient length. 1265 | - Try to be consistent within a docstring whether you use this additional whitespace. 1266 | - Follow one of the following templates for types and functions when possible: 1267 | 1268 | Type Template (should be skipped if it is redundant with the constructor(s) docstring): 1269 | 1270 | ```julia 1271 | """ 1272 | MyArray{T, N} 1273 | 1274 | My super awesome array wrapper! 1275 | 1276 | # Fields 1277 | - `data::AbstractArray{T, N}`: stores the array being wrapped 1278 | - `metadata::Dict`: stores metadata about the array 1279 | """ 1280 | struct MyArray{T, N} <: AbstractArray{T, N} 1281 | data::AbstractArray{T, N} 1282 | metadata::Dict 1283 | end 1284 | ``` 1285 | 1286 | Function Template (only required for exported functions): 1287 | 1288 | ```julia 1289 | """ 1290 | mysearch(array::MyArray{T}, val::T; verbose = true) where {T} -> Int 1291 | 1292 | Searches the `array` for the `val`. For some reason we don't want to use Julia's 1293 | builtin search :) 1294 | 1295 | # Arguments 1296 | - `array::MyArray{T}`: the array to search 1297 | - `val::T`: the value to search for 1298 | 1299 | # Keywords 1300 | - `verbose::Bool = true`: print out progress details 1301 | 1302 | # Returns 1303 | - `Int`: the index where `val` is located in the `array` 1304 | 1305 | # Throws 1306 | - `NotFoundError`: I guess we could throw an error if `val` isn't found. 1307 | """ 1308 | function mysearch(array::AbstractArray{T}, val::T) where {T} 1309 | ... 1310 | end 1311 | ``` 1312 | 1313 | - The `@doc doc""" """` formulation from the Markdown standard library should be used whenever 1314 | there is LaTeX. 1315 | - Only public fields of types must be documented. Undocumented fields are considered non-public internals. 1316 | - If your method contains lots of arguments or keywords, you may want to exclude them from the method 1317 | signature on the first line and instead use `args...` and/or `kwargs...`. 1318 | 1319 | ```julia 1320 | """ 1321 | Manager(args...; kwargs...) -> Manager 1322 | 1323 | A cluster manager which spawns workers. 1324 | 1325 | # Arguments 1326 | 1327 | - `min_workers::Integer`: The minimum number of workers to spawn or an exception is thrown 1328 | - `max_workers::Integer`: The requested number of workers to spawn 1329 | 1330 | # Keywords 1331 | 1332 | - `definition::AbstractString`: Name of the job definition to use. Defaults to the 1333 | definition used within the current instance. 1334 | - `name::AbstractString`: ... 1335 | - `queue::AbstractString`: ... 1336 | """ 1337 | function Manager(...) 1338 | ... 1339 | end 1340 | ``` 1341 | 1342 | - Feel free to document multiple methods for a function within the same docstring. Be careful to only do this for functions you have defined. 1343 | 1344 | ```julia 1345 | """ 1346 | Manager(max_workers; kwargs...) 1347 | Manager(min_workers:max_workers; kwargs...) 1348 | Manager(min_workers, max_workers; kwargs...) 1349 | 1350 | A cluster manager which spawns workers. 1351 | 1352 | # Arguments 1353 | 1354 | - `min_workers::Int`: The minimum number of workers to spawn or an exception is thrown 1355 | - `max_workers::Int`: The requested number of workers to spawn 1356 | 1357 | # Keywords 1358 | 1359 | - `definition::AbstractString`: Name of the job definition to use. Defaults to the 1360 | definition used within the current instance. 1361 | - `name::AbstractString`: ... 1362 | - `queue::AbstractString`: ... 1363 | """ 1364 | function Manager end 1365 | 1366 | ``` 1367 | 1368 | - If the documentation for bullet-point exceeds 92 characters, the line should be wrapped and slightly indented. 1369 | Avoid aligning the text to the `:`. 1370 | 1371 | ```julia 1372 | """ 1373 | ... 1374 | 1375 | # Keywords 1376 | - `definition::AbstractString`: Name of the job definition to use. Defaults to the 1377 | definition used within the current instance. 1378 | """ 1379 | ``` 1380 | 1381 | ### Error Handling 1382 | 1383 | - `error("string")` should be avoided. Defining and throwing exception types is preferred. See the 1384 | [manual on exceptions for more details](https://docs.julialang.org/en/v1/manual/control-flow/#Exception-Handling). 1385 | - Try to avoid `try/catch`. Use it as minimally as possible. Attempt to catch potential issues before running 1386 | code, not after. 1387 | 1388 | ### Arrays 1389 | 1390 | - Avoid splatting (`...`) whenever possible. Prefer iterators such as `collect`, `vcat`, `hcat`, etc. instead. 1391 | 1392 | ### Line Endings 1393 | 1394 | Always use Unix style `\n` line ending. 1395 | 1396 | ### VS-Code Settings 1397 | 1398 | If you are a user of VS Code we recommend that you have the following options in your Julia syntax specific settings. 1399 | To modify these settings, open your VS Code Settings with CMD+, (Mac OS) or CTRL+, (other OS), and add to your `settings.json`: 1400 | 1401 | ```json 1402 | { 1403 | "[julia]": { 1404 | "editor.detectIndentation": false, 1405 | "editor.insertSpaces": true, 1406 | "editor.tabSize": 4, 1407 | "files.insertFinalNewline": true, 1408 | "files.trimFinalNewlines": true, 1409 | "files.trimTrailingWhitespace": true, 1410 | "editor.rulers": [92], 1411 | "files.eol": "\n" 1412 | }, 1413 | } 1414 | ``` 1415 | Additionally, you may find the [Julia VS-Code plugin](https://github.com/julia-vscode/julia-vscode) useful. 1416 | 1417 | ### JuliaFormatter 1418 | 1419 | **Note: the** `sciml` **style is only available in** `JuliaFormatter v1.0` **or later** 1420 | 1421 | One can add `.JuliaFormatter.toml` with the content 1422 | ```toml 1423 | style = "sciml" 1424 | ``` 1425 | in the root of a repository, and run 1426 | ```julia 1427 | using JuliaFormatter, SomePackage 1428 | format(joinpath(dirname(pathof(SomePackage)), "..")) 1429 | ``` 1430 | to format the package automatically. 1431 | 1432 | Add [FormatCheck.yml](https://github.com/SciML/ModelingToolkit.jl/blob/master/.github/workflows/FormatCheck.yml) to enable the formatting CI. The CI will fail if the repository needs additional formatting. Thus, one should run `format` before committing. 1433 | 1434 | # References 1435 | 1436 | Many of these style choices were derived from the [Julia style guide](https://docs.julialang.org/en/v1/manual/style-guide/), 1437 | the [YASGuide](https://github.com/jrevels/YASGuide), and the [Blue style guide](https://github.com/invenia/BlueStyle#module-imports). 1438 | Additionally, many tips and requirements from the [JuliaHub Secure Coding Practices](https://juliahub.com/company/resources/white-papers/secure-julia-coding-practices/index.html) 1439 | manual were incorporated into this style. 1440 | -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 3 | SciMLStyle = "59b18918-51b4-4202-aa6b-1b76b310ffb6" 4 | 5 | [compat] 6 | Documenter = "1" 7 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter 2 | 3 | dir = @__DIR__() * "/.." 4 | cp(joinpath(dir, "README.md"), joinpath(dir, "docs", "src", "index.md"), force = true) 5 | 6 | makedocs(sitename = "SciML Style Guide for Julia", 7 | authors = "Chris Rackauckas", 8 | modules = Module[], 9 | clean = true, doctest = false, linkcheck = true, 10 | linkcheck_ignore = ["https://docs.julialang.org/en/v1/base/math/#Base.:^-Tuple{Number,%20Number}"], 11 | format = Documenter.HTML(assets = ["assets/favicon.ico"], 12 | canonical = "https://docs.sciml.ai/SciMLStyle/stable/"), 13 | pages = [ 14 | "SciML Style Guide for Julia" => "index.md", 15 | ]) 16 | 17 | deploydocs(; 18 | repo = "github.com/SciML/SciMLStyle", 19 | devbranch = "main") 20 | -------------------------------------------------------------------------------- /docs/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/SciMLStyle/75933d54cf71b7eafeff3f279ee953233c3b96fa/docs/src/assets/favicon.ico -------------------------------------------------------------------------------- /docs/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/SciMLStyle/75933d54cf71b7eafeff3f279ee953233c3b96fa/docs/src/assets/logo.png -------------------------------------------------------------------------------- /src/SciMLStyle.jl: -------------------------------------------------------------------------------- 1 | module SciMLStyle 2 | end 3 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/SciMLStyle/75933d54cf71b7eafeff3f279ee953233c3b96fa/test/runtests.jl --------------------------------------------------------------------------------