Skip to content

The boundaries list

SyntaxDescription
class MyError : System.Exception { }Declaring an exception type
public delegate int Op(int a);Declaring a delegate
private Op F;A delegate-typed field
/// <summary>…</summary>Doc comments
#nullable enable / string? sNullable annotation
[NotNull]Null-analysis attribute
[NotNullWhen(true)] out string sNot-null-when-true specification
[return: MaybeNull]Maybe-null return specification
[AllowNull]Allow-null specification
#nullable disableDisabling the check
SyntaxDescriptionErrorReasonInstead
throw;Rethrow a caught exceptionTUKI0001runtimeRepresent failure with a return value or state
catch { … }Catch without writing a typeTUKI0001runtimeSame as above
yield return / yield breakReturn values one at a timeTUKI0001, TUKI0099runtimePack them into an array and return it
IEnumerator<int> Nums()Return an iteratorTUKI0001, TUKI0099runtimeSame as above
async Task Run()Write an async methodTUKI0001, TUKI0101runtimeSplit it into events
async void Run()An async method with no return valueTUKI0001, TUKI0101runtimeSame as above
await …Wait for completionTUKI0001runtimeSame as above
System.Func<int,int> f = x => x + 1;Store a lambda expression in a variableTUKI0108, TUKI0001runtimeCall a method by name
() => nA lambda expression that captures an outer variableTUKI0108, TUKI0001runtimePass the value you want to capture as an argument, and call a method by name
[DllImport("x")]Call an external libraryTUKI0001runtimeUse a runtime method
dynamic d = 1; d.ToString()Resolve a member by name at runtimeTUKI0102, TUKI0099runtimeWrite the type
A declaration compiling doesn’t mean you can use it. For both exception types and delegates, the declaration passes the compiler, and you’re rejected only once you write the line that uses it.

LINQ is rejected not because of iterators or delegates, but because the namespace doesn’t exist. Just the line using System.Linq; produces CS0234, and the compiler says it can’t find System.Linq.

Forms that pass through silently are confusing precisely because they aren’t rejected. You can write doc comments and null annotations, but the runtime meaning doesn’t change.

You can declare an exception type, but you can’t throw or catch it.

using UnityEngine;
using Tsukimi;
public class MyError : System.Exception { }
public class ExCustom : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}

You can declare a delegate type, but you can’t create a value of that type.

using UnityEngine;
using Tsukimi;
public delegate int Op(int a);
public class DlDeclared : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}

You can declare the field, but you can’t create a value to put in it.

using UnityEngine;
using Tsukimi;
public delegate int Op(int a);
public class DlField : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
private Op F;
}

You can write doc comments, but they don’t appear anywhere.

using UnityEngine;
using Tsukimi;
public class DcComment : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
/// <summary>description</summary>
private int Helper() { return 1; }
}

A type with ? attached affects C#‘s checking, but the runtime behaviour doesn’t change.

using UnityEngine;
using Tsukimi;
public class NrAnnot : TsukimiBehaviour
{
void Start()
{
#nullable enable
string? s = null;
Debug.Log(s == null); // => true
}
}

[NotNull] also affects only C#‘s analysis.

using UnityEngine;
using Tsukimi;
public class NrAttr : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
[System.Diagnostics.CodeAnalysis.NotNull]
private string S = "a";
}

[NotNullWhen(true)] is conveyed to C#‘s analysis, but the runtime behaviour doesn’t change.

using UnityEngine;
using Tsukimi;
public class NrNotNullWhen : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
private bool Try([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string s) { s = "a"; return true; }
}

[return: MaybeNull] also affects only C#‘s analysis.

using UnityEngine;
using Tsukimi;
public class NrMaybeNull : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
[return: System.Diagnostics.CodeAnalysis.MaybeNull]
private string Get() { return null; }
}

[AllowNull] also affects only C#‘s analysis.

using UnityEngine;
using Tsukimi;
public class NrAllowNull : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
[System.Diagnostics.CodeAnalysis.AllowNull]
private string S = "a";
}

From the line where you write #nullable disable onward, null-safety checking is turned off.

#nullable enable
using UnityEngine;
using Tsukimi;
public class NrDisable : TsukimiBehaviour
{
void Start()
{
#nullable disable
string s = null;
Debug.Log(s == null); // => true
}
}

Boundaries aren’t only on the language side. They’re also on the side of the types you touch. What this environment exposes is a subset of Unity’s and VRChat’s types. Writing a type that isn’t there into a variable gets rejected with TUKI0102. Even if a type exists, not all of the calls in it are necessarily present. Calls that aren’t exposed are rejected with TUKI0101.

This document doesn’t keep a list of what’s in which type. Unity’s types are in Unity’s Script Reference, and VRChat’s types are in VRChat’s creator documentation. What this document covers is the boundary on the language side, which comes before that.