Introduction
Since 2016 (with interruptions) I have been developing my own programming language. The name of this language is “Ü”. Why Ü? Because I wanted a one-letter name, and all the letters from the AZ set are already taken.
This article has the task of acquainting the public with this language, to give a general idea of it. The article does not intend to describe the language completely, a more complete description of the language, if necessary, will be given in subsequent articles.
Why do you need another language?
I examined a number of existing statically-typed compiled more or less well-known languages, and came to the conclusion that none of them quite suits me. All of them have fatal flaws.
Specific points:
- C - too low level and unsafe
- C ++ - inherited many low-level flaws, new ways to shoot yourself in the foot, lack of reflection
- D - garbage collector, separate reference types
- Java is a garbage collector, all composite types are referenced, strong attachment to a virtual machine. A lot of this also applies to JVM-based languages.
- C # - flaws are much like Java
- Rust - the need to explicitly take links and explicitly dereference them, (subjectively) the danger of an approach when everything is an expression and returns a result, the presence of an explicit indication of compliance with the protocol, the lack of inheritance
- Go - garbage collector, lack of templates
- Swift - reference types, the need to explicitly indicate compliance with the protocol
Having discovered the imperfection of all the above languages, I decided to create my own, devoid of, as it seems to me, all the flaws.
General information
Ü is a compiled, statically typed language with strong typing. The language contains free functions, structures, classes, methods for structures and classes. Existing types of types - fundamental, structural (structures, classes), arrays of constant size, tuples, enumerations, pointers to functions. Classes can participate in inheritance and have virtual functions. The language supports templates, there are templates of classes and structures, aliases of types, functions and methods. There is an overload of functions and operators (with some restrictions).
The language supports destructors for classes and structures. Destructors are used to manage resources, including memory.
What is already there
Implemented LLVM based compiler. The compiler supports all platforms that supports LLVM. The compiler is not yet able to create purely executable files, but is able to generate object files, assembler code, binary or text llvm code. There is a possibility of communication with C code, there is a utility that makes it easier to write Ü-headers for C libraries.
Targets and goals
The language is created in such a way as to catch the maximum number of typical errors at the compilation stage. Language design decisions are made primarily based on this task. The second goal is the (relative) ease of writing programs and the ease of reading them.
The language does not contain constructs that could provoke the writing of erroneous code, and also does not contain features that could significantly complicate the understanding of what is happening in the program.
Despite the points above, the issue of performance cannot be ignored. Therefore, at the expense of performance, language design decisions are not made.
So, let's look at what are the moments in Ü that correspond to the stated goals.
Type system
Unlike C ++, in Ü there are no pointers and references as types. There are no pointers at all, there are links, but only as stack links, function reference arguments, reference fields. Also, mut / imut modifiers are not part of a type, but are part of a variable, reference, function argument, field.
Thanks to this simplification, there is a greater understanding of where which type is, especially in boilerplate code. There are no doubts whether a link or a variable will be declared, doubts as a result of typeof (analogue of decltype from C ++), doubts about constant / non-constant.
The flip side of this simplification is the need for separate processing of consistency and links in templates. Although, this adds clarity when the constancy parameter is passed to the template as an explicitly separate argument.
Initializers
It is impossible to declare a variable and not initialize it. Any byte of the declared variable must be initialized.
Fundamental (and some other) types require mandatory initialization:
var i32 x= 22, y(12345);
:
struct Vec{ f32 x; f32 y; }
...
var Vec v{ .x= -56.1f, .y= 42.0f };
var Vec v_uninit; //
, :
struct Vec
{
f32 x; f32 y;
fn constructor()
( x= 0.0f, y= 0.0f )
{}
}
...
var Vec v; //
, .
, :
struct Vec
{
f32 x= 0.0f;
f32 y= 0.0f;
}
, [] , :
var [ i32, 4 ] a[ 1, 1, 3, 4 ];
var tup[ i32, f32 ] t[ 8, 0.5f ];
var [ f32, 16 ] a_uninit; // , .
var[ i32, 3 ] aa[ 0, 1 ]; // ,
C++, , . , , — .
. , , — . this, - /, :
struct Vec
{
f32 x; f32 y;
fn constructor()
( x= y, y= 0.0f ) // , «y»
{}
fn constructor( f32 val )
( x= val, y= x ) // . «x» «y»
{}
}
. , , . .
:
var i32 mut x= 0;
var i32 &mut ref0= x;
++x; // ,
var i32 &imut ref1= x; // , , . .
:
var i32 mut x= 0;
var i32 &imut ref0= x;
var i32 &mut ref1= x; // , ,
:
var i32 mut x= 0;
var i32 &mut ref0= x;
var i32 &mut ref1= ref0; // , . . ,
:
fn Mutate( i32 &mut x, i32 &mut y );
...
var i32 mut x= 0;
Mutate( x, x ); // ,
. C++
std::vector<int> vec;
vec.push_back(1);
int& ref= vec.front();
vec.push_back(2); // , ref
Ü :
var ust::vector</i32/> mut vec;
vec.push_back(1);
var i32 &imut ref= vec.front();
vec.push_back(2); // , vec,
( ). — polymorph, interface, abstract . . , .
:
- . , , .
:
class A interface
{
fn virtual pure Foo(this);
}
- . . . , this, , . . . abstract call, , , C++.
:
class A abstract
{
fn virtual pure Foo(this);
fn virtual Bar(this){}
i32 x= 0;
}
- . . . - , .
:
class A interface
{
fn virtual pure Foo(this);
}
class B : A
{
fn virtual override Foo(this){}
}
- . , , .
. .
fn virtual pure Foo(this); // . , , .
fn virtual Foo(this){} // . , .
fn virtual override Foo(ths){} // , . , .
fn virtual final Foo(this){} // , . , . - , .
2019— , , Ü . . , , , , .
:
fn Foo( i32 mut x ) : i32
{
while(x < 100 )
{
x+= 10;
continue;
x+= 20; // ,
}
return x;
++x; // ,
}
:
fn Foo( i32 x ) : i32
{
if( x < 10 ) { return 0; }
else if( x > 100 ) { return 1; }
// ,
}
else , return .
, - , halt ( ). , , , return.
. , (. ). () .
:
struct CallableObject
{
i32 &mut x;
op()( mut this )
{
++x;
}
}
...
var i32 mut x= 0;
var CallableObject mut obj{ .x= x };
auto thread= ust::thread_create( move(obj) );
++x; // , «x», «thread»
++ obj.x; //
- , , - , .
(mutable C++), . . . , - , .
, ? — shared_ptr_mt . , , lock_mut() lock_imut(), . « , ».
:
struct Incrementer
{
ust::shared_ptr_mt_mut</ i32 /> ptr;
op()( mut this )
{
auto mut lock= ptr.lock_mut(); // «lock»
++lock.get_ref();
}
}
...
var ust::shared_ptr_mt_mut</ i32 /> ptr(0);
var size_type mut i(0);
var ust::vector</ ust::thread</Incrementer/> /> mut threads;
while( i < size_type(64) )
{
var Incrementer incrementer{ .ptr= ptr };
threads.push_back( ust::thread_create(incrementer) );
++i;
}
threads.clear(); // .
halt if( ptr.lock_imut().get_ref() != 64 ); // , ,
, , . . «shared». , , , - , «shared».
. , , constexpr . , .
, Ü, - , C.
C++, (=, +=, *= . .) , , . ++ –.
:
if( x = 0 ) {}
x++; // ++
x += x += x; // , +=
x+= x++ + ++x; //
. void, .
, , . :
auto a = select( condition ? x : y );
select .
. . . .
? , - :
- . - , , .
- . . - NullPointerException - , - . , , . . .
- . , , , . - - , . , .
- . , , .
, , ?
, . . «» , , std::optional/std::variant C++ Option/Result Rust.
, -, , halt. ( Ü ) , , , .
, - . , import, , . C++, , .
. , *.cpp *.hpp C++.
, , , .
, , . , , , . , - , .
→
→
, , .
→
.
, . . , .