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::demonamespace.
-
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.demopackage.
-
csharp_package:- Defines the C# namespace for the generated code.
- Example:
"Company.Demo"will place C# classes in theCompany.Demonamespace.
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:
-
readmethod:- May return an error (
error) - Is mutable (
mut) - Has a mutable
bufferparameter
- May return an error (
-
writemethod:- May return an error (
error) - Is immutable (no
mut)
- May return an error (
-
seekmethod:- Is mutable (
mut) - Does not indicate error handling
- Is mutable (
-
querymethod:- May return an error (
error) - Is immutable (no
mut)
- May return an error (
-
executemethod:- May return an error (
error) - Is mutable (
mut) - Has a mutable
txparameter
- May return an error (
The effects of these annotations vary by target language:
-
In Go:
errormethods will return an additionalerrortypemuthas no effect (Go doesn't have const methods)
-
In C++:
errormethods will be declared to throw exceptionsmutmethods will be non-const,mutparameters will be non-const references
-
In Java:
errormethods will be declared to throw exceptionsmuthas no direct effect but can be used in custom code generation
-
In Rust:
errormethods will return aResulttypemutmethods will take&mut self,mutparameters will be&mutreferences
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
@nextannotation 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.