Annotation Guide
Annotations in Next provide a powerful mechanism to customize code generation, add metadata to declarations, and control language-specific behavior. This comprehensive guide covers the syntax, usage, and best practices for annotations in Next, with numerous examples to illustrate each concept.
Annotation Syntax
Annotations in Next start with the @
symbol and can be applied to packages, declarations, constants, enums (and their fields), structs (and their fields), and interfaces (and their methods).
The general syntax for annotations is:
@annotation_name[(parameter1[=value1], parameter2[=value2], ...)]
Annotations can take three forms:
- No parameters:
@annotation_name
- With parameters:
@annotation_name(param1=value1, param2=value2)
- With boolean parameters (no value implies
true
):@annotation_name(param1, param2=false)
Examples:
@next(deprecated)
@json(name="user_id")
@validate(minLength=3, maxLength=50)
@auth(required) // Equivalent to @auth(required=true)
For boolean parameters, @annotation(param)
is equivalent to @annotation(param=true)
.
Built-in Annotations
@next
The @next
annotation is a built-in, reserved annotation in Next. It's used to pass information to the Next compiler and should not be used as a custom annotation.
Package-level @next
The @next
annotation at the package level allows you to specify language-specific package information. Each language has its own parameter, all using the _package
suffix:
@next(
go_package="github.com/username/repo/demo",
cpp_package="repo::demo",
c_package="DEMO_",
java_package="com.example.demo",
csharp_package="Company.Demo",
go_imports="fmt.Printf, *net/http.Client, *encoding/json.Decoder"
)
package demo;
Let's break down each *_package
parameter:
-
go_package
:- Specifies the full import path for the Go package.
- Example:
"github.com/username/repo/demo"
-
cpp_package
:- Defines the C++ namespace for the generated code.
- Example:
"repo::demo"
will generate code in therepo::demo
namespace.
-
c_package
:- Provides a prefix for C code generation, typically used for function and type names.
- Example:
"DEMO_"
might generate functions likeDEMO_Function()
.
-
java_package
:- Sets the Java package for the generated code.
- Example:
"com.example.demo"
will place Java classes in thecom.example.demo
package.
-
csharp_package
:- Defines the C# namespace for the generated code.
- Example:
"Company.Demo"
will place C# classes in theCompany.Demo
namespace.
Additional parameters:
- go_imports: Specifies additional imports for Go (see go_imports section for details).
Examples of usage in different languages:
// Go
package demo
// The actual import path "github.com/username/repo/demo" is specified in the build system or go.mod file
// ...
// C++
namespace repo {
namespace demo {
// ...
}
}
// C
PREFIX_Function();
// ...
// Java
package com.example.demo;
// ...
// C#
namespace Company.Demo
{
// ...
}
Use these language-specific package parameters to ensure your generated code fits well with the target language's package or namespace conventions.
The exact effect of these parameters may depend on the code generation templates used. Always refer to your project's specific code generation rules for the most accurate information.
go_imports
The go_imports
parameter in the @next
annotation for packages specifies additional imports for Go:
@next(
go_package="github.com/username/repo/demo",
go_imports="fmt.Printf, *net/http.Client, *encoding/json.Decoder, time.Now, *context.Context"
)
package demo;
The go_imports
parameter accepts a comma-separated list of import paths and items. Use the following syntax:
- For functions, variables, or constants:
pkg.Item
- For types:
*pkg.Type
(note the*
prefix for types)
When specifying types in go_imports
, always prefix with *
for types to generate correct unused declarations.
This will generate the following in your Go file:
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
var _ = fmt.Printf
var _ = (*http.Client)(nil)
var _ = (*json.Decoder)(nil)
var _ = time.Now
var _ = (*context.Context)(nil)
Enum-level @next
@next(type=int8)
enum Color {
Red = 1;
Green = 2;
Blue = 3;
}
Struct and Interface-level @next
@next(
go_alias="net/http.Handler",
java_alias="java.util.function.Function<com.sun.net.httpserver.HttpExchange, String>",
cpp_alias="std::function<void(const HttpRequest&, HttpResponse&)>",
csharp_alias="System.Func<HttpContext, Task>"
)
interface HTTPHandler {}
@next(
go_alias="time.Time",
java_alias="java.time.Instant",
cpp_alias="std::chrono::system_clock::time_point",
csharp_alias="System.DateTime"
)
struct Timestamp {
int64 seconds;
int32 nanos;
}
When a type is aliased using <lang>_alias
, the Next compiler will not generate code for that type in the specified language. Instead, it will use the aliased type directly.
Method-level @next
The @next
annotation can be applied to interface methods to indicate specific behaviors or characteristics. Here are the key parameters for method-level @next
annotations:
error
: Indicates that the method may return an error or throw an exception.mut
: When applied to the method, indicates that the method is mutable (non-const in C++).
When applied to a parameter, mut
indicates that the parameter is mutable.
Examples:
interface FileSystem {
@next(error, mut)
read(@next(mut) bytes buffer) int;
@next(error)
write(string filename, bytes data) bool;
@next(mut)
seek(int offset, int whence) int;
}
interface Database {
@next(error)
query(string sql) Result;
@next(error, mut)
execute(@next(mut) Transaction tx) bool;
}
In these examples:
-
read
method:- May return an error (
error
) - Is mutable (
mut
) - Has a mutable
buffer
parameter
- May return an error (
-
write
method:- May return an error (
error
) - Is immutable (no
mut
)
- May return an error (
-
seek
method:- Is mutable (
mut
) - Does not indicate error handling
- Is mutable (
-
query
method:- May return an error (
error
) - Is immutable (no
mut
)
- May return an error (
-
execute
method:- May return an error (
error
) - Is mutable (
mut
) - Has a mutable
tx
parameter
- May return an error (
The effects of these annotations vary by target language:
-
In Go:
error
methods will return an additionalerror
typemut
has no effect (Go doesn't have const methods)
-
In C++:
error
methods will be declared to throw exceptionsmut
methods will be non-const,mut
parameters will be non-const references
-
In Java:
error
methods will be declared to throw exceptionsmut
has no direct effect but can be used in custom code generation
-
In Rust:
error
methods will return aResult
typemut
methods will take&mut self
,mut
parameters will be&mut
references
Use error
to clearly indicate methods that can fail, allowing for proper error handling in generated code.
Use mut
judiciously to express intent about state modification, especially useful in languages with const-correctness like C++.
available
The available
parameter in the @next
annotation controls conditional code generation for specific programming languages:
@next(available="go|java")
interface HTTPServer {
@next(error)
handle(string path, HTTPHandler handler);
}
@next(available="!cpp & !java", go_alias="complex128)
struct Complex {
float64 real;
float64 imag;
}
@next(available="c | cpp | go")
struct LowLevelBuffer {
bytes data;
int size;
}
The available
parameter accepts a boolean expression that can include language identifiers, operators (&
, |
, !
, (
, )
), and the literals true
and false
.
Use available
to generate language-specific interfaces, structs, or other declarations without needing to maintain separate files for each language. This allows you to write your Next code once and generate appropriate output for multiple target languages.
The available
parameter is strictly for specifying target programming languages. It should not be used for specifying platforms, operating systems, or other non-language conditions.
Custom Annotations
Custom annotations can be created for specific needs and processed in custom templates or plugins. Here are some examples:
@next(deprecated")
struct User {
int id;
@json(omitempty)
string name;
}
@message(type=101, req)
struct LoginRequest {
string username;
string password;
}
@auth(required)
@rate_limit(requests=100, per="minute")
interface UserService {
getUser(int id) User;
}
@db(table="products", primaryKey="id")
struct Product {
int id;
string name;
float price;
@validate(min=0, max=100)
int percentage;
}
@async
@retry(maxAttempts=3, delay="1s")
interface DataFetcher {
fetchData(string url) bytes;
}
Using Annotations in Templates
Annotations can be accessed and used within templates to customize code generation. Here are some examples:
Checking for Annotation Presence
{{- if .Annotations.Contains "deprecated" -}}
// Deprecated: {{.Annotations.deprecated}}
{{- end -}}
Accessing Annotation Parameters
{{- if .Annotations.Contains "json" -}}
`json:"{{or (.Annotations.json.name) (.Name | camelCase)}}{{if .Annotations.json.omitempty}},omitempty{{end}}"`
{{- end -}}
Handling Aliased Types
{{- if .Annotations.next.go_alias -}}
{{.Annotations.next.go_alias}}
{{- else -}}
{{.Name}}
{{- end -}}
Processing Custom Annotations
{{- if .Annotations.Contains "validate" -}}
@Min({{.Annotations.validate.min}})
@Max({{.Annotations.validate.max}})
{{- end -}}
{{- if .Annotations.Contains "db" -}}
@Table(name = "{{.Annotations.db.table}}")
@Id("{{.Annotations.db.primaryKey}}")
{{- end -}}
{{- if .Annotations.Contains "async" -}}
@Async
{{- end -}}
{{- if .Annotations.Contains "retry" -}}
@Retry(maxAttempts = {{.Annotations.retry.maxAttempts}}, delay = {{.Annotations.retry.delay}})
{{- end -}}
Generating Documentation from Annotations
{{- if .Doc -}}
/**
{{.Doc.Format " * " | align}}
{{- with .Annotations.next.deprecated}}
* @deprecated {{.}}
{{- end}}
{{- if .Annotations.Contains "param"}}
{{- range $name, $desc := .Annotations.param}}
* @param {{$name}} {{$desc}}
{{- end}}
{{- end}}
{{- if .Annotations.Contains "return"}}
* @return {{.Annotations.return}}
{{- end}}
*/
{{- end -}}
Best Practices
- Use annotations consistently across your project.
- Leverage language-specific annotations to optimize generated code for each target language.
- Document custom annotations thoroughly, especially if they're used by other team members or in open-source projects.
- Use the
@next
annotation judiciously, as it has special meaning in the Next compiler. - When using
available
, consider the maintainability of your code across different language targets. - For
go_imports
, group related imports together and order them logically. - In templates, always check if an annotation exists before trying to access its values to avoid errors.
- Use custom annotations to encapsulate complex generation logic, making your templates cleaner and more maintainable.
- When creating custom annotations, choose clear and descriptive names that indicate their purpose.
- Consider the impact of annotations on code readability and maintain a balance between expressiveness and simplicity.
While annotations are powerful, overuse can lead to cluttered and hard-to-read code. Strike a balance between expressiveness and simplicity.