Chapter 7 - Attributes
This chapter will briefly go over some of the attributes you can use when writing programs.
Attributes
An attribute acts like a tag that attaches itself to other pieces of code. There are several ways of applying an attribute to code (don’t worry about what private
means at the moment, we’ll get to that, but for now it is enough to know that it is an attribute).
To apply an attribute to a single declaration, simply write the keyword before the declaration itself:
private fn add(a: i32, b: i32) i32 { return a + b; }
You can apply an attribute to multiple declarations at once by using braces:
private {
fn func1() {} // This is marked private.
fn func2() {} // This too.
}
fn func3() {} // But this isn't.
You can apply an attribute to all following declarations at the same level by using a colon:
struct S
{
private:
fn func1() {} // Private.
fn func2() {} // Private.
}
fn func3() {} // Not private.
Note that some attributes are the opposite of other attributes, and you can ‘turn off’ these attributes by using their opposites:
private {
fn func1() {} // Private.
public func2() {} // Not private.
}
private:
fn func3() {} // Private.
public:
fn func4() {} // Not private.
Now we have a basic understanding of how we can apply attributes, let’s go over some of the things they can do.
Access Levels
Volt defines three access level attributes: public
, private
, and protected
. The access level determines how code that is not in the module a declaration is defined in can access a given declaration.
Public
public
is the default access level. If private
or protected
are never used, a declaration will be considered public
. A public
declaration can be used by any module that imports it.
Private
private
means that any code outside of the module where the private
declaration lives will be unable to access it, and an attempt will generate an error at compile time.
module a;
fn add(a: i32, b: i32) i32
{
return addImplementation(a, b); // We can use this because it's the same module.
}
private fn addImplementation(a: i32, b: i32) i32 { return a + b; }
And in another module:
module b;
import a;
fn main() i32
{
v1 := add(10, 2); // This is fine, 'add' is public.
v2 := addImplementation(10, 2); // error: tried to access private symbol addImplementation
}
Protected
protected
is an access level that only occurs in class
declarations (see Chapter 8). To the outside world, protected
behaves like private
, in that it restricts access to protected
symbols.
What makes it different is it allows child classes to see protected
symbols. That is to say, if class A
in module a
defines a private
method, class B
in module b
couldn’t call that method, and neither could fn bfunc
in module b
. But if it defines a protected
method, class B
could call it, while bfunc
could not. Note that this applies to methods and global
functions that are declared inside of functions. Given
module a;
class A
{
protected global fn foo() i32 ...
}
In one module, and if in another module we have:
module b;
class B : A
{
global fn bar() i32 { return A.foo(); /* okay */ }
}
class C /* doesn't inherit from A */
{
global fn bar() i32 { return A.foo(); /* error */ }
}
Access Levels And Overloading
Multiple functions with the same name, different arguments, but different access levels, cannot be overloaded.
private fn foo() {}
public fn foo(a: i32) {} // error!
The only exceptions are constructors. This is because a private
constructor is a way of ‘disabling’ a constructor.
Other Attributes
Properties
You can mark a function with @property
if it takes one argument, or no arguments with a non-void
return value.
class Person {
string _name;
@property string name() {
return _name;
}
@property void name(string n) {
_name = n;
}
}
...
auto p = new Person();
p.name = "Selma"; // Calls second function as name("Selma").
string s = p.name; // Calls first function as name();