mollusk 0e4acfb8f2 fix incorrect folder name for julia-0.6.x
Former-commit-id: ef2c7401e0876f22d2f7762d182cfbcd5a7d9c70
2018-06-11 03:28:36 -07:00

31 lines
25 KiB
HTML

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>Arrays with custom indices · The Julia Language</title><script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-28835595-6', 'auto');
ga('send', 'pageview');
</script><link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/4.2.0/normalize.min.css" rel="stylesheet" type="text/css"/><link href="https://fonts.googleapis.com/css?family=Lato|Roboto+Mono" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css" rel="stylesheet" type="text/css"/><script>documenterBaseURL=".."</script><script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.min.js" data-main="../assets/documenter.js"></script><script src="../siteinfo.js"></script><script src="../../versions.js"></script><link href="../assets/documenter.css" rel="stylesheet" type="text/css"/><link href="../assets/julia-manual.css" rel="stylesheet" type="text/css"/></head><body><nav class="toc"><a href="../index.html"><img class="logo" src="../assets/logo.png" alt="The Julia Language logo"/></a><h1>The Julia Language</h1><select id="version-selector" onChange="window.location.href=this.value" style="visibility: hidden"></select><form class="search" id="search-form" action="../search.html"><input id="search-query" name="q" type="text" placeholder="Search docs"/></form><ul><li><a class="toctext" href="../index.html">Home</a></li><li><span class="toctext">Manual</span><ul><li><a class="toctext" href="../manual/introduction.html">Introduction</a></li><li><a class="toctext" href="../manual/getting-started.html">Getting Started</a></li><li><a class="toctext" href="../manual/variables.html">Variables</a></li><li><a class="toctext" href="../manual/integers-and-floating-point-numbers.html">Integers and Floating-Point Numbers</a></li><li><a class="toctext" href="../manual/mathematical-operations.html">Mathematical Operations and Elementary Functions</a></li><li><a class="toctext" href="../manual/complex-and-rational-numbers.html">Complex and Rational Numbers</a></li><li><a class="toctext" href="../manual/strings.html">Strings</a></li><li><a class="toctext" href="../manual/functions.html">Functions</a></li><li><a class="toctext" href="../manual/control-flow.html">Control Flow</a></li><li><a class="toctext" href="../manual/variables-and-scoping.html">Scope of Variables</a></li><li><a class="toctext" href="../manual/types.html">Types</a></li><li><a class="toctext" href="../manual/methods.html">Methods</a></li><li><a class="toctext" href="../manual/constructors.html">Constructors</a></li><li><a class="toctext" href="../manual/conversion-and-promotion.html">Conversion and Promotion</a></li><li><a class="toctext" href="../manual/interfaces.html">Interfaces</a></li><li><a class="toctext" href="../manual/modules.html">Modules</a></li><li><a class="toctext" href="../manual/documentation.html">Documentation</a></li><li><a class="toctext" href="../manual/metaprogramming.html">Metaprogramming</a></li><li><a class="toctext" href="../manual/arrays.html">Multi-dimensional Arrays</a></li><li><a class="toctext" href="../manual/linear-algebra.html">Linear algebra</a></li><li><a class="toctext" href="../manual/networking-and-streams.html">Networking and Streams</a></li><li><a class="toctext" href="../manual/parallel-computing.html">Parallel Computing</a></li><li><a class="toctext" href="../manual/dates.html">Date and DateTime</a></li><li><a class="toctext" href="../manual/interacting-with-julia.html">Interacting With Julia</a></li><li><a class="toctext" href="../manual/running-external-programs.html">Running External Programs</a></li><li><a class="toctext" href="../manual/calling-c-and-fortran-code.html">Calling C and Fortran Code</a></li><li><a class="toctext" href="../manual/handling-operating-system-variation.html">Handling Operating System Variation</a></li><li><a class="toctext" href="../manual/environment-variables.html">Environment Variables</a></li><li><a class="toctext" href="../manual/embedding.html">Embedding Julia</a></li><li><a class="toctext" href="../manual/packages.html">Packages</a></li><li><a class="toctext" href="../manual/profile.html">Profiling</a></li><li><a class="toctext" href="../manual/stacktraces.html">Stack Traces</a></li><li><a class="toctext" href="../manual/performance-tips.html">Performance Tips</a></li><li><a class="toctext" href="../manual/workflow-tips.html">Workflow Tips</a></li><li><a class="toctext" href="../manual/style-guide.html">Style Guide</a></li><li><a class="toctext" href="../manual/faq.html">Frequently Asked Questions</a></li><li><a class="toctext" href="../manual/noteworthy-differences.html">Noteworthy Differences from other Languages</a></li><li><a class="toctext" href="../manual/unicode-input.html">Unicode Input</a></li></ul></li><li><span class="toctext">Standard Library</span><ul><li><a class="toctext" href="../stdlib/base.html">Essentials</a></li><li><a class="toctext" href="../stdlib/collections.html">Collections and Data Structures</a></li><li><a class="toctext" href="../stdlib/math.html">Mathematics</a></li><li><a class="toctext" href="../stdlib/numbers.html">Numbers</a></li><li><a class="toctext" href="../stdlib/strings.html">Strings</a></li><li><a class="toctext" href="../stdlib/arrays.html">Arrays</a></li><li><a class="toctext" href="../stdlib/parallel.html">Tasks and Parallel Computing</a></li><li><a class="toctext" href="../stdlib/linalg.html">Linear Algebra</a></li><li><a class="toctext" href="../stdlib/constants.html">Constants</a></li><li><a class="toctext" href="../stdlib/file.html">Filesystem</a></li><li><a class="toctext" href="../stdlib/io-network.html">I/O and Network</a></li><li><a class="toctext" href="../stdlib/punctuation.html">Punctuation</a></li><li><a class="toctext" href="../stdlib/sort.html">Sorting and Related Functions</a></li><li><a class="toctext" href="../stdlib/pkg.html">Package Manager Functions</a></li><li><a class="toctext" href="../stdlib/dates.html">Dates and Time</a></li><li><a class="toctext" href="../stdlib/iterators.html">Iteration utilities</a></li><li><a class="toctext" href="../stdlib/test.html">Unit Testing</a></li><li><a class="toctext" href="../stdlib/c.html">C Interface</a></li><li><a class="toctext" href="../stdlib/libc.html">C Standard Library</a></li><li><a class="toctext" href="../stdlib/libdl.html">Dynamic Linker</a></li><li><a class="toctext" href="../stdlib/profile.html">Profiling</a></li><li><a class="toctext" href="../stdlib/stacktraces.html">StackTraces</a></li><li><a class="toctext" href="../stdlib/simd-types.html">SIMD Support</a></li></ul></li><li><span class="toctext">Developer Documentation</span><ul><li><a class="toctext" href="reflection.html">Reflection and introspection</a></li><li><span class="toctext">Documentation of Julia&#39;s Internals</span><ul><li><a class="toctext" href="init.html">Initialization of the Julia runtime</a></li><li><a class="toctext" href="ast.html">Julia ASTs</a></li><li><a class="toctext" href="types.html">More about types</a></li><li><a class="toctext" href="object.html">Memory layout of Julia Objects</a></li><li><a class="toctext" href="eval.html">Eval of Julia code</a></li><li><a class="toctext" href="callconv.html">Calling Conventions</a></li><li><a class="toctext" href="compiler.html">High-level Overview of the Native-Code Generation Process</a></li><li><a class="toctext" href="functions.html">Julia Functions</a></li><li><a class="toctext" href="cartesian.html">Base.Cartesian</a></li><li><a class="toctext" href="meta.html">Talking to the compiler (the <code>:meta</code> mechanism)</a></li><li><a class="toctext" href="subarrays.html">SubArrays</a></li><li><a class="toctext" href="sysimg.html">System Image Building</a></li><li><a class="toctext" href="llvm.html">Working with LLVM</a></li><li><a class="toctext" href="stdio.html">printf() and stdio in the Julia runtime</a></li><li><a class="toctext" href="boundscheck.html">Bounds checking</a></li><li><a class="toctext" href="locks.html">Proper maintenance and care of multi-threading locks</a></li><li class="current"><a class="toctext" href="offset-arrays.html">Arrays with custom indices</a><ul class="internal"><li><a class="toctext" href="#Generalizing-existing-code-1">Generalizing existing code</a></li><li><a class="toctext" href="#Writing-custom-array-types-with-non-1-indexing-1">Writing custom array types with non-1 indexing</a></li><li><a class="toctext" href="#Summary-1">Summary</a></li></ul></li><li><a class="toctext" href="libgit2.html">Base.LibGit2</a></li><li><a class="toctext" href="require.html">Module loading</a></li></ul></li><li><span class="toctext">Developing/debugging Julia&#39;s C code</span><ul><li><a class="toctext" href="backtraces.html">Reporting and analyzing crashes (segfaults)</a></li><li><a class="toctext" href="debuggingtips.html">gdb debugging tips</a></li><li><a class="toctext" href="valgrind.html">Using Valgrind with Julia</a></li><li><a class="toctext" href="sanitizers.html">Sanitizer support</a></li></ul></li></ul></li></ul></nav><article id="docs"><header><nav><ul><li>Developer Documentation</li><li>Documentation of Julia&#39;s Internals</li><li><a href="offset-arrays.html">Arrays with custom indices</a></li></ul><a class="edit-page" href="https://github.com/JuliaLang/julia/blob/master/doc/src/devdocs/offset-arrays.md"><span class="fa"></span> Edit on GitHub</a></nav><hr/><div id="topbar"><span>Arrays with custom indices</span><a class="fa fa-bars" href="#"></a></div></header><h1><a class="nav-anchor" id="Arrays-with-custom-indices-1" href="#Arrays-with-custom-indices-1">Arrays with custom indices</a></h1><p>Julia 0.5 adds experimental support for arrays with arbitrary indices. Conventionally, Julia&#39;s arrays are indexed starting at 1, whereas some other languages start numbering at 0, and yet others (e.g., Fortran) allow you to specify arbitrary starting indices. While there is much merit in picking a standard (i.e., 1 for Julia), there are some algorithms which simplify considerably if you can index outside the range <code>1:size(A,d)</code> (and not just <code>0:size(A,d)-1</code>, either). Such array types are expected to be supplied through packages.</p><p>The purpose of this page is to address the question, &quot;what do I have to do to support such arrays in my own code?&quot; First, let&#39;s address the simplest case: if you know that your code will never need to handle arrays with unconventional indexing, hopefully the answer is &quot;nothing.&quot; Old code, on conventional arrays, should function essentially without alteration as long as it was using the exported interfaces of Julia.</p><h2><a class="nav-anchor" id="Generalizing-existing-code-1" href="#Generalizing-existing-code-1">Generalizing existing code</a></h2><p>As an overview, the steps are:</p><ul><li><p>replace many uses of <code>size</code> with <code>indices</code></p></li><li><p>replace <code>1:length(A)</code> with <code>linearindices(A)</code>, and <code>length(A)</code> with <code>length(linearindices(A))</code></p></li><li><p>replace explicit allocations like <code>Array{Int}(size(B))</code> with <code>similar(Array{Int}, indices(B))</code></p></li></ul><p>These are described in more detail below.</p><h3><a class="nav-anchor" id="Background-1" href="#Background-1">Background</a></h3><p>Because unconventional indexing breaks deeply-held assumptions throughout the Julia ecosystem, early adopters running code that has not been updated are likely to experience errors. The most frustrating bugs would be incorrect results or segfaults (total crashes of Julia). For example, consider the following function:</p><pre><code class="language-julia">function mycopy!(dest::AbstractVector, src::AbstractVector)
length(dest) == length(src) || throw(DimensionMismatch(&quot;vectors must match&quot;))
# OK, now we&#39;re safe to use @inbounds, right? (not anymore!)
for i = 1:length(src)
@inbounds dest[i] = src[i]
end
dest
end</code></pre><p>This code implicitly assumes that vectors are indexed from 1. Previously that was a safe assumption, so this code was fine, but (depending on what types the user passes to this function) it may no longer be safe. If this code continued to work when passed a vector with non-1 indices, it would either produce an incorrect answer or it would segfault. (If you do get segfaults, to help locate the cause try running julia with the option <code>--check-bounds=yes</code>.)</p><p>To ensure that such errors are caught, in Julia 0.5 both <code>length</code> and <code>size</code><strong>should</strong> throw an error when passed an array with non-1 indexing. This is designed to force users of such arrays to check the code, and inspect it for whether it needs to be generalized.</p><h3><a class="nav-anchor" id="Using-indices-for-bounds-checks-and-loop-iteration-1" href="#Using-indices-for-bounds-checks-and-loop-iteration-1">Using <code>indices</code> for bounds checks and loop iteration</a></h3><p><code>indices(A)</code> (reminiscent of <code>size(A)</code>) returns a tuple of <code>AbstractUnitRange</code> objects, specifying the range of valid indices along each dimension of <code>A</code>. When <code>A</code> has unconventional indexing, the ranges may not start at 1. If you just want the range for a particular dimension <code>d</code>, there is <code>indices(A, d)</code>.</p><p>Base implements a custom range type, <code>OneTo</code>, where <code>OneTo(n)</code> means the same thing as <code>1:n</code> but in a form that guarantees (via the type system) that the lower index is 1. For any new <a href="../stdlib/arrays.html#Core.AbstractArray"><code>AbstractArray</code></a> type, this is the default returned by <code>indices</code>, and it indicates that this array type uses &quot;conventional&quot; 1-based indexing. Note that if you don&#39;t want to be bothered supporting arrays with non-1 indexing, you can add the following line:</p><pre><code class="language-julia">@assert all(x-&gt;isa(x, Base.OneTo), indices(A))</code></pre><p>at the top of any function.</p><p>For bounds checking, note that there are dedicated functions <code>checkbounds</code> and <code>checkindex</code> which can sometimes simplify such tests.</p><h3><a class="nav-anchor" id="Linear-indexing-(linearindices)-1" href="#Linear-indexing-(linearindices)-1">Linear indexing (<code>linearindices</code>)</a></h3><p>Some algorithms are most conveniently (or efficiently) written in terms of a single linear index, <code>A[i]</code> even if <code>A</code> is multi-dimensional. In &quot;true&quot; linear indexing, the indices always range from <code>1:length(A)</code>. However, this raises an ambiguity for one-dimensional arrays (a.k.a., <code>AbstractVector</code>): does <code>v[i]</code> mean linear indexing, or Cartesian indexing with the array&#39;s native indices?</p><p>For this reason, if you want to use linear indexing in an algorithm, your best option is to get the index range by calling <code>linearindices(A)</code>. This will return <code>indices(A, 1)</code> if <code>A</code> is an <code>AbstractVector</code>, and the equivalent of <code>1:length(A)</code> otherwise.</p><p>In a sense, one can say that 1-dimensional arrays always use Cartesian indexing. To help enforce this, it&#39;s worth noting that <code>sub2ind(shape, i...)</code> and <code>ind2sub(shape, ind)</code> will throw an error if <code>shape</code> indicates a 1-dimensional array with unconventional indexing (i.e., is a <code>Tuple{UnitRange}</code> rather than a tuple of <code>OneTo</code>). For arrays with conventional indexing, these functions continue to work the same as always.</p><p>Using <code>indices</code> and <code>linearindices</code>, here is one way you could rewrite <code>mycopy!</code>:</p><pre><code class="language-julia">function mycopy!(dest::AbstractVector, src::AbstractVector)
indices(dest) == indices(src) || throw(DimensionMismatch(&quot;vectors must match&quot;))
for i in linearindices(src)
@inbounds dest[i] = src[i]
end
dest
end</code></pre><h3><a class="nav-anchor" id="Allocating-storage-using-generalizations-of-similar-1" href="#Allocating-storage-using-generalizations-of-similar-1">Allocating storage using generalizations of <code>similar</code></a></h3><p>Storage is often allocated with <code>Array{Int}(dims)</code> or <code>similar(A, args...)</code>. When the result needs to match the indices of some other array, this may not always suffice. The generic replacement for such patterns is to use <code>similar(storagetype, shape)</code>. <code>storagetype</code> indicates the kind of underlying &quot;conventional&quot; behavior you&#39;d like, e.g., <code>Array{Int}</code> or <code>BitArray</code> or even <code>dims-&gt;zeros(Float32, dims)</code> (which would allocate an all-zeros array). <code>shape</code> is a tuple of <a href="../stdlib/numbers.html#Core.Integer"><code>Integer</code></a> or <code>AbstractUnitRange</code> values, specifying the indices that you want the result to use.</p><p>Let&#39;s walk through a couple of explicit examples. First, if <code>A</code> has conventional indices, then <code>similar(Array{Int}, indices(A))</code> would end up calling <code>Array{Int}(size(A))</code>, and thus return an array. If <code>A</code> is an <code>AbstractArray</code> type with unconventional indexing, then <code>similar(Array{Int}, indices(A))</code> should return something that &quot;behaves like&quot; an <code>Array{Int}</code> but with a shape (including indices) that matches <code>A</code>. (The most obvious implementation is to allocate an <code>Array{Int}(size(A))</code> and then &quot;wrap&quot; it in a type that shifts the indices.)</p><p>Note also that <code>similar(Array{Int}, (indices(A, 2),))</code> would allocate an <code>AbstractVector{Int}</code> (i.e., 1-dimensional array) that matches the indices of the columns of <code>A</code>.</p><h3><a class="nav-anchor" id="Deprecations-1" href="#Deprecations-1">Deprecations</a></h3><p>In generalizing Julia&#39;s code base, at least one deprecation was unavoidable: earlier versions of Julia defined <code>first(::Colon) = 1</code>, meaning that the first index along a dimension indexed by <code>:</code> is 1. This definition can no longer be justified, so it was deprecated. There is no provided replacement, because the proper replacement depends on what you are doing and might need to know more about the array. However, it appears that many uses of <code>first(::Colon)</code> are really about computing an index offset; when that is the case, a candidate replacement is:</p><pre><code class="language-julia">indexoffset(r::AbstractVector) = first(r) - 1
indexoffset(::Colon) = 0</code></pre><p>In other words, while <code>first(:)</code> does not itself make sense, in general you can say that the offset associated with a colon-index is zero.</p><h2><a class="nav-anchor" id="Writing-custom-array-types-with-non-1-indexing-1" href="#Writing-custom-array-types-with-non-1-indexing-1">Writing custom array types with non-1 indexing</a></h2><p>Most of the methods you&#39;ll need to define are standard for any <code>AbstractArray</code> type, see <a href="../manual/interfaces.html#man-interface-array-1">Abstract Arrays</a>. This page focuses on the steps needed to define unconventional indexing.</p><h3><a class="nav-anchor" id="Do-**not**-implement-size-or-length-1" href="#Do-**not**-implement-size-or-length-1">Do <strong>not</strong> implement <code>size</code> or <code>length</code></a></h3><p>Perhaps the majority of pre-existing code that uses <code>size</code> will not work properly for arrays with non-1 indices. For that reason, it is much better to avoid implementing these methods, and use the resulting <code>MethodError</code> to identify code that needs to be audited and perhaps generalized.</p><h3><a class="nav-anchor" id="Do-**not**-annotate-bounds-checks-1" href="#Do-**not**-annotate-bounds-checks-1">Do <strong>not</strong> annotate bounds checks</a></h3><p>Julia 0.5 includes <code>@boundscheck</code> to annotate code that can be removed for callers that exploit <code>@inbounds</code>. Initially, it seems far preferable to run with bounds checking always enabled (i.e., omit the <code>@boundscheck</code> annotation so the check always runs).</p><h3><a class="nav-anchor" id="Custom-AbstractUnitRange-types-1" href="#Custom-AbstractUnitRange-types-1">Custom <code>AbstractUnitRange</code> types</a></h3><p>If you&#39;re writing a non-1 indexed array type, you will want to specialize <code>indices</code> so it returns a <code>UnitRange</code>, or (perhaps better) a custom <code>AbstractUnitRange</code>. The advantage of a custom type is that it &quot;signals&quot; the allocation type for functions like <code>similar</code>. If we&#39;re writing an array type for which indexing will start at 0, we likely want to begin by creating a new <code>AbstractUnitRange</code>, <code>ZeroRange</code>, where <code>ZeroRange(n)</code> is equivalent to <code>0:n-1</code>.</p><p>In general, you should probably <em>not</em> export <code>ZeroRange</code> from your package: there may be other packages that implement their own <code>ZeroRange</code>, and having multiple distinct <code>ZeroRange</code> types is (perhaps counterintuitively) an advantage: <code>ModuleA.ZeroRange</code> indicates that <code>similar</code> should create a <code>ModuleA.ZeroArray</code>, whereas <code>ModuleB.ZeroRange</code> indicates a <code>ModuleB.ZeroArray</code> type. This design allows peaceful coexistence among many different custom array types.</p><p>Note that the Julia package <a href="https://github.com/JuliaArrays/CustomUnitRanges.jl">CustomUnitRanges.jl</a> can sometimes be used to avoid the need to write your own <code>ZeroRange</code> type.</p><h3><a class="nav-anchor" id="Specializing-indices-1" href="#Specializing-indices-1">Specializing <code>indices</code></a></h3><p>Once you have your <code>AbstractUnitRange</code> type, then specialize <code>indices</code>:</p><pre><code class="language-julia">Base.indices(A::ZeroArray) = map(n-&gt;ZeroRange(n), A.size)</code></pre><p>where here we imagine that <code>ZeroArray</code> has a field called <code>size</code> (there would be other ways to implement this).</p><p>In some cases, the fallback definition for <code>indices(A, d)</code>:</p><pre><code class="language-julia">indices(A::AbstractArray{T,N}, d) where {T,N} = d &lt;= N ? indices(A)[d] : OneTo(1)</code></pre><p>may not be what you want: you may need to specialize it to return something other than <code>OneTo(1)</code> when <code>d &gt; ndims(A)</code>. Likewise, in <code>Base</code> there is a dedicated function <code>indices1</code> which is equivalent to <code>indices(A, 1)</code> but which avoids checking (at runtime) whether <code>ndims(A) &gt; 0</code>. (This is purely a performance optimization.) It is defined as:</p><pre><code class="language-julia">indices1(A::AbstractArray{T,0}) where {T} = OneTo(1)
indices1(A::AbstractArray) = indices(A)[1]</code></pre><p>If the first of these (the zero-dimensional case) is problematic for your custom array type, be sure to specialize it appropriately.</p><h3><a class="nav-anchor" id="Specializing-similar-1" href="#Specializing-similar-1">Specializing <code>similar</code></a></h3><p>Given your custom <code>ZeroRange</code> type, then you should also add the following two specializations for <code>similar</code>:</p><pre><code class="language-julia">function Base.similar(A::AbstractArray, T::Type, shape::Tuple{ZeroRange,Vararg{ZeroRange}})
# body
end
function Base.similar(f::Union{Function,DataType}, shape::Tuple{ZeroRange,Vararg{ZeroRange}})
# body
end</code></pre><p>Both of these should allocate your custom array type.</p><h3><a class="nav-anchor" id="Specializing-reshape-1" href="#Specializing-reshape-1">Specializing <code>reshape</code></a></h3><p>Optionally, define a method</p><pre><code class="language-none">Base.reshape(A::AbstractArray, shape::Tuple{ZeroRange,Vararg{ZeroRange}}) = ...</code></pre><p>and you can <code>reshape</code> an array so that the result has custom indices.</p><h2><a class="nav-anchor" id="Summary-1" href="#Summary-1">Summary</a></h2><p>Writing code that doesn&#39;t make assumptions about indexing requires a few extra abstractions, but hopefully the necessary changes are relatively straightforward.</p><p>As a reminder, this support is still experimental. While much of Julia&#39;s base code has been updated to support unconventional indexing, without a doubt there are many omissions that will be discovered only through usage. Moreover, at the time of this writing, most packages do not support unconventional indexing. As a consequence, early adopters should be prepared to identify and/or fix bugs. On the other hand, only through practical usage will it become clear whether this experimental feature should be retained in future versions of Julia; consequently, interested parties are encouraged to accept some ownership for putting it through its paces.</p><footer><hr/><a class="previous" href="locks.html"><span class="direction">Previous</span><span class="title">Proper maintenance and care of multi-threading locks</span></a><a class="next" href="libgit2.html"><span class="direction">Next</span><span class="title">Base.LibGit2</span></a></footer></article></body></html>