📜 ⬆️ ⬇️

Compare Swift and Rust


The reason for writing the article was the publication of the source code of the Swift language - it became interesting to me to get acquainted with it. Immediately, the similarity of syntax with another young programming language called Rust caught my eye, and similar syntax constructions also looked at a similar field of application of these languages. Both languages ​​have strong static typing with local type inference, both are compiled directly into machine code. Both languages ​​absorbed many tricks from the world of functional programming. Both Swift and Rust have the means to run code written in C, which makes it easy to write wrappers over a huge number of libraries. Both languages ​​are considered as a replacement for existing system languages ​​such as C, C ++, ObjectiveC. So what they have in common, and what is different?


Basic syntax



Immediately, I’ll make a reservation that I’m not trying to tell readers the basics of two programming languages ​​at once, and if something is unclear, I recommend turning to Rustbook or Swiftbook for help.
')
First, let's try to compare the simplest programs:

Rusty
fn main() { let i:i32 = 16; let mut f = 8.0; f *= 2.0; println!("Hello Rust! within vars {} and {}", i, f); } 


Swift
 let i:Int = 10 var f = 15.0 f /= 2 print("Hello Swift within vars \(i) \(f)") 


You can see that let means the same thing here and there, and the var keyword in Swift is similar to the combination of let mut in Rust. But there are differences: in Rust in all numeric types the size is unambiguously indicated, and Swift follows the traditions of C. In this matter, Rust looks lower level.
Interpolation of strings in Rust is done using the format! Macro, which is called in the bowels of println !, and in Swift, this is a feature of the language itself, but I did not find in the documentation how to set the formatting options.
There is an interesting difference in the interpretation of ";": for Rust, this is a sign of the end of the expression, which allows you to do some elegant things, for example, the last expression inside the function automatically becomes the return value. Swift simply follows the traditions of pascal, where the semicolon simply separates the operators on the same line.

Now let's try to create a function that takes a function from two arguments and converts it to a function from one:

Rusty
 fn apply<F: 'static>(f: F, v1: i32) -> Box<Fn(i32) -> ()> where F: Fn(i32, i32) -> () { Box::new(move |v2| f(v1, v2)) } fn print_sum(a: i32, b: i32) -> () { println!("Rust: sum a and b is {}", a + b); } fn main() { let a = 2; let b = 5; print_sum(a, b); let f = print_sum; let f2 = apply(f, b); f2(a); } 


swift
 func apply(f: (_: Int, _: Int) -> (), _ v1: Int) -> (_: Int) -> () { return {(c: Int) -> () in return f(v1, c) } } func print_sum(a: Int, second b: Int) -> () { print("Swift: sum a and b is \(a+b)") } let a = 2; let b = 5; print_sum(a, second:b) let f2 = apply(print_sum, b) f2(a) 


There are already clearly noticeable differences, I will leave behind the purely syntactic differences of the type of external names of named parameters in Swift or generalizations in Rust, and proceed to consider a more significant difference. The Swift approach is noticeably higher-level: the compiler itself decides how to store our result function, but for Rust, we had to explicitly pack it into a box, because the moved closures are a dimensionless type.
Let's check whose functions lambda work faster:

Benchmark code
Rusty
 fn apply<F: 'static>(f: F, v1: i32) -> Box<Fn(i32) -> i32> where F: Fn(i32, i32) -> i32 { Box::new(move |v2| f(v1, v2)) } fn make_sum(a: i32, b: i32) -> i32 { a + b } fn main() { let a = 2; let b = 5; let c = make_sum(a, b); println!("Rust: c is {}", c); let f2 = apply(make_sum, b); let mut d = 0; for i in 0..1000000000 { d = f2(i); } println!("Rust: d is {}", d); } 


swift
 func apply(f: (_: Int, _: Int) -> Int, _ v1: Int) -> (_: Int) -> Int { return {(c: Int) -> Int in return f(v1, c) } } func make_sum(a: Int, second b: Int) -> Int { return a + b } let a = 2; let b = 5; let c = make_sum(a, second:b) print("Swift: c is \(c)") let f2 = apply(make_sum, b) f2(a) var d = 0; for i in 0...1000000000 { d = f2(i); } print("Swift: d is \(d)"); 




Final result: 4.0 seconds for Rust versus 1.17 for Swift. It turns out that in the case of a more abstract code, the compiler has more opportunities for optimization, but on the ruRust / general channel I was prompted by a method that may or may not look so beautiful, but it allows the optimizer to give everything in full. Ultimately, Rust considered the whole cycle right at compile time, which is very cool.
In the able hands of Rust allows you to work wonders.

Quick version code
 struct Curry<'a> { f: &'a Fn(i32, i32) -> i32, v1: i32 } impl<'a> Curry<'a> { fn new<F: 'static>(f: &'a F, v1: i32) -> Curry<'a> where F: Fn(i32, i32) -> i32 { Curry { f: f, v1: v1 } } fn call(&'a self, v2: i32) -> i32 { (*self.f)(self.v1, v2) } } fn make_sum(a: i32, b: i32) -> i32 { a + b } fn main() { let a = 2; let b = 5; let c = make_sum(a, b); println!("Rust: c is {}", c); let borrow = &make_sum; let f2 = Curry::new(borrow, b); let mut d = 0; for i in 0..1000000000 { d = f2.call(i); } println!("Rust: d is {}", d); } 



Enumerations and pattern matching:



Both languages ​​have great possibilities for pattern matching, in both languages ​​there are algebraic data types. Let's try to write a simple example using these features: we will try to implement mathematical operations. To do this, we will need to make our transfers recursive. As a bonus, we will try to print the names of our operations on the screen using protocols (types).
Rusty
 enum MathOperation { Value(i32), Sum(Box<MathOperation>, Box<MathOperation>), Mul(Box<MathOperation>, Box<MathOperation>) } trait HasName { fn name(&self) -> &'static str; } impl HasName for MathOperation { fn name(&self) -> &'static str { match *self { MathOperation::Value(..) => "Value", MathOperation::Sum(..) => "Sum", MathOperation::Mul(..) => "Mul" } } } impl MathOperation { fn solve(&self) -> i32 { match *self { MathOperation::Value(i) => i, MathOperation::Sum(ref left, ref right) => left.solve() + right.solve(), MathOperation::Mul(ref left, ref right) => left.solve() * right.solve() } } } fn main() { let op = MathOperation::Sum(Box::new(MathOperation::Value(10)), Box::new(MathOperation::Mul(Box::new(MathOperation::Value(20)), Box::new(MathOperation::Value(2))))); ; println!("Rust: op is {} solved {}", op.name(), op.solve()); } 


Swift
 enum MathOperation { case Value(Int) indirect case Sum(MathOperation, MathOperation) indirect case Mul(MathOperation, MathOperation) func solve() -> Int { switch self { case .Value(let value): return value case .Sum(let left, let right): return left.solve() + right.solve() case .Mul(let left, let right): return left.solve() * right.solve() } } } protocol HasName { func name() -> String; } extension MathOperation : HasName { func name() -> String { switch self { case .Value(_): return "Value" case .Sum(_, _): return "Sum" case .Mul(_, _): return "Mul" } } } let op = MathOperation.Sum(MathOperation.Value(10), MathOperation.Mul(MathOperation.Value(20), MathOperation.Value(2))) print("Swift: op is \(op.name()) solved \(op.solve())"); 


We can note that in Rust, in order to organize recursive transfers, it was necessary to pack them into a Box container, because their size can be arbitrary and we cannot know at the compilation stage how much memory we need for this. In Swift, the word indirect is used to refer to recursive enumerations, but given its memory model, the question arises, is the compiler itself unable to figure out how to allocate memory? Apparently this keyword is introduced rather for a person.
We can also see that impl and extension, in principle, perform approximately similar work, and types are similar to protocols. But in Swift the approach is more compromise: it is not necessary to add methods as extensions, you can specify them directly in the declaration of the enumeration.

Now let's just look at a couple of examples of pattern matching:

Rust (examples taken from Rust by example)

 match some_value { Ok(value) => println!("got a value: {}", value), Err(_) => println!("an error occurred"), } enum OptionalTuple { Value(i32, i32, i32), Missing, } let x = OptionalTuple::Value(5, -2, 3); match x { OptionalTuple::Value(..) => println!("Got a tuple!"), OptionalTuple::Missing => println!("No such luck."), } let x = 1; match x { 1 ... 5 => println!("one through five"), _ => println!("anything"), } let x = 1; match x { e @ 1 ... 5 => println!("got a range element {}", e), _ => println!("anything"), } 

Swift (sample code taken from Swiftbook)
 let count = 3000000000000 let countedThings = "stars in the Milky Way" var naturalCount: String switch count { case 0: naturalCount = "no" case 1...3: naturalCount = "a few" case 4...9: naturalCount = "several" case 10...99: naturalCount = "tens of" case 100...999: naturalCount = "hundreds of" case 1000...999999: naturalCount = "thousands of" default: naturalCount = "millions and millions of" } print("There are \(naturalCount) \(countedThings).") //  "There are millions and millions of stars in the Milky Way." let somePoint = (1, 1) switch somePoint { case (0, 0): print("(0, 0) is at the origin") case (_, 0): print("(\(somePoint.0), 0) is on the x-axis") case (0, _): print("(0, \(somePoint.1)) is on the y-axis") case (-2...2, -2...2): print("(\(somePoint.0), \(somePoint.1)) is inside the box") default: print("(\(somePoint.0), \(somePoint.1)) is outside of the box") }//  "(1, 1) is inside the box let anotherPoint = (2, 0) switch anotherPoint { case (let x, 0): print("on the x-axis with an x value of \(x)") case (0, let y): print("on the y-axis with ay value of \(y)") case let (x, y): print("somewhere else at (\(x), \(y))") } //  "on the x-axis with an x value of 2 


Here, in general, everything is very similar. There should be no dips in the mapping, you can specify ranges of values, there is the possibility of binding values, you can use tuples in the mapping. Although Rust also allows the use of whole structures for matching. You can set additional conditions through if and where, respectively. But at the same time there are additional flow control statements in Swift. Although I'm not sure that using them is a good idea.

A more realistic example



Let's try to write a Bresenham rasterization algorithm . Only not in the usual form, but in the form for n-dimensional vectors and without the use of real numbers. This can all be useful when studying a short course in computer graphics .

To begin, just try to create a 3-dimensional vector and define for it the operation of taking the index and comparison:
Rusty
 #[derive(Copy, Clone, PartialEq)] struct Vec3 { x: i32, y: i32, z: i32 } impl Index<usize> for Vec3 { type Output = i32; fn index<'a>(&'a self, i: usize) -> &'a Self::Output { match i { 0 => &self.x, 1 => &self.y, 2 => &self.z, _ => panic!("Wrong index"), } } } impl IndexMut<usize> for Vec3 { fn index_mut<'a>(&'a mut self, i: usize) -> &'a mut Self::Output { match i { 0 => &mut self.x, 1 => &mut self.y, 2 => &mut self.z, _ => panic!("Wrong index"), } } } 

swift
 struct Vector3 { var x: Int; var y: Int; var z: Int; subscript(i: Int) -> Int { get { precondition(i >= 0 && i < 3, "Index out-of-bounds") switch i { case 0: return self.x case 1: return self.y case 2: return self.z default: return 0 } } set { precondition(i >= 0 && i < 3, "Index out-of-bounds") switch i { case 0: self.x = newValue case 1: self.y = newValue case 2: self.z = newValue default: break } } } } func == (left: Vector3, right: Vector3) -> Bool { return (left.x == right.x) && (left.y == right.y) && (left.z == right.z) } func != (left: Vector3, right: Vector3) -> Bool { return !(left == right) } 

In Rust, in fact, operators are implemented through types, if the structure implements the Index type, then the operator [] can be used for it, in Swift, index taking operators are written using the subscript keyword and allow you to add special preconditions. Rust relies on ordinary asserts. In the case of other operators, Swift allows you to both redefine existing ones and define your own, and with an indication of priorities, associativity. All of this can be a great help when writing math libraries, making the code more similar to the original math expressions. Rust is able to automatically create implementations of certain types through the derive attribute, which makes it possible not to write the code itself in many trivial cases.

Let's now try to create a convenient buffer for storing the image so that the color of any pixel can be accessed through index operators. In this case, we make the requirement that the pixel map be able to different color depth, so we will make it generalized:

rust
 struct GenericPixmap<T> { w: usize, h: usize, data: Vec<T> } impl<T> GenericPixmap<T> where T: Copy + Clone { fn new(w: usize, h: usize, fill_value: T) -> GenericPixmap<T> { GenericPixmap { w: w, h: h, data: vec![fill_value; w*h] } } } impl<T> Index<usize> for GenericPixmap<T> where T: Copy + Clone { type Output = [T]; fn index<'a>(&'a self, i: usize) -> &'a Self::Output { let from = i*self.w; let to = from+self.w; &self.data[from..to] } } impl<T> IndexMut<usize> for GenericPixmap<T> where T: Copy + Clone { fn index_mut<'a>(&'a mut self, i: usize) -> &'a mut Self::Output { let from = i*self.w; let to = from+self.w; &mut self.data[from..to] } } type Pixmap = GenericPixmap<u32>; 


swift
 struct GenericPixmap<T> { let w: Int let h: Int var data: [T] init(width: Int, height: Int, fillValue: T) { self.w = width self.h = height self.data = [T](count: w*h, repeatedValue: fillValue) } func indexIsValid(x: Int, _ y: Int) -> Bool { return x >= 0 && x < w && y >= 0 && y < h } subscript(x: Int, y: Int) -> T { get { precondition(indexIsValid(x, y), "Index out-of-bounds") return data[x * y + y] } set { precondition(indexIsValid(x,y), "Index out-of-bounds") data[x * y + y] = newValue } } } typealias Pixmap = GenericPixmap<UInt32> 


The rules for generalizations in Rust are stricter and we need to explicitly state that the template type should be able to copy and create its own copy. In Swift, however, for the structure fields, their variability can be explicitly set. You can also notice the init keyword. This is a class or structure constructor, there may be several of them, they can delegate their powers to each other. As a result, this results in a rather complex and multi-step process, which, nevertheless, precisely guarantees that each member will be initialized. In Rust, there is a termwise initialization and an agreement that the object should be created by the static new function. If the process promises to be difficult, then it is recommended to use factories. As for static functions, Rust's syntax in this sense follows the traditions of python, while Swift follows C ++.
I want to note that the index operator in Swift can take any number of arguments of any type, so there you can write an operator that immediately receives a specific array element, in Rust you need to create a slice.

Now let's create a type of Canvas, which allows us to draw without thinking about the implementation of the process itself.
rust
 trait Canvas { fn set_pixel(&mut self, x: usize, y:usize, color:u32); } impl Canvas for Pixmap { fn set_pixel(&mut self, x: usize, y:usize, color:u32) { self[x][y] = color; } } 

swift
 protocol Canvas { func setPixel(x: Int, _ y: Int, color: UInt32); } class MyCanvas : Canvas { var pixmap: Pixmap init(width: Int, height: Int, fillValue: UInt32) { self.pixmap = Pixmap(width:width, height:height, fillValue:fillValue) } func setPixel(x: Int, _ y: Int, color: UInt32) { pixmap[x, y] = color } } 


Unfortunately, I could not quickly figure out how to implement an extension for the GenericPixmap type, so I decided to create a new class called MyCanvas that would implement the Canvas protocol, unlike Rust in Swift, you can inherit from the protocols and not only.

Now we come to the most interesting - the implementation of the Brezenham algorithm. We want to draw a line from the point (x1, y1, z1) to the point (x2, y2, z2), for this we need to take (| x2-x1 |, | y2-y1 |, | z2-z1 |) steps in the direction , which depends on the signs of the expression (x2-x1, y2-y1, z2-z1).
So, we need to go (rx, ry, rz) steps in the directions (sx, sy, sz), for this we find the axis along which we need to perform the greatest number of steps. The movement at each step will be equal to (rx / r [max, ry / r [max], rz / r [max]), and the step will occur only if the total movement d has become more than one, then the axis makes a step, and from total displacement is subtracted one. I.e:
 d[i] += r[i] / r[max] if d[i] >= 1 { r[i] -= s[i]; d[i] -= 1; } 

It is easy to see that if you multiply the condition by rmax, then you can do without the division operation.
 d[i] += r[i] if d[i] >= r[max] { r[i] -= s[i]; d[i] -= r[max]; } 

As a result, this variant of the algorithm works for any number of coordinates and avoids floating-point operations, which are almost always more expensive than operations with integers.

Let's try to write a generator that creates a sequence of points between end nodes a and b. The point a will be included in the sequence.

rust
 struct RasterState { step: Vec3, d: Vec3, major_axis: usize, } struct LineRasterizer { from: Vec3, to: Vec3, state: Option<RasterState> } impl LineRasterizer { fn new(from: Vec3, to: Vec3) -> LineRasterizer { LineRasterizer { from: from, to: to, state: None } } fn next_point(&mut self) -> Option<Vec3> { match self.state { None => { let mut state = RasterState { step: Vec3 { x: 0, y: 0, z: 0 }, d: Vec3 { x: 0, y: 0, z: 0 }, major_axis: 0 }; let mut max = 0; for i in 0..3 { let d = self.to[i] - self.from[i]; state.step[i] = if d > 0 { 1 } else { -1 }; let d = d.abs(); if d > max { max = d; state.major_axis = i as usize; }; } self.state = Some(state); Some(self.from) }, Some(ref mut state) => { if self.from == self.to { None } else { let from = self.from; let to = self.to; let calc_residual_steps = |axis| { (to[axis] - from[axis]).abs() }; self.from[state.major_axis] += state.step[state.major_axis]; let rs_base = calc_residual_steps(state.major_axis); for i in 0..3 { let rs = calc_residual_steps(i); if rs > 0 && i != state.major_axis { state.d[i] += rs; if state.d[i] >= rs_base { state.d[i] -= rs_base; self.from[i] += state.step[i]; } } } Some(self.from) } }, } } } 

swift
 class LineRaster { class State { var step: Vector3 var d: Vector3 var majorAxis: Int init() { self.step = Vector3(x: 0, y: 0, z: 0) self.d = Vector3(x: 0, y: 0, z: 0) self.majorAxis = 0 } } var from: Vector3 let to: Vector3 var state: State? init(from: Vector3, to: Vector3) { self.from = from self.to = to } func next_point() -> Vector3? { if let state = self.state { if (self.from == self.to) { return nil } else { let calsResidualSteps = {axis in return abs(self.to[axis] - self.from[axis])} self.from[state.majorAxis] += state.step[state.majorAxis]; let rsBase = calsResidualSteps(state.majorAxis); for i in 0..<3 { let rs = calsResidualSteps(i); if rs > 0 && i != state.majorAxis { state.d[i] += rs; if state.d[i] >= rsBase { state.d[i] -= rsBase; self.from[i] += state.step[i]; } } } return self.from } } else { let state = State() var max = 0; for i in 0..<3 { let d = self.to[i] - self.from[i]; state.step[i] = d > 0 ? 1 : -1; let da = abs(d); if da > max { max = da; state.majorAxis = i; }; } self.state = state return self.from } } } 


I decided to make the state of the generator as an optional value, this allows us to easily and immediately return the starting point from from the generator without the need for additional flags. In Rust, optional values ​​are made simply through the enum Option, while in Swift they are part of the language, which makes it easy to describe optional call chains without too much syntactic noise.
In Rust, an advanced possession system is used to tell it that we are borrowing a State from the listing by reference, you need to write the keyword ref. In Swift, State is by default the reference data type, and move semantics in the language is not yet observed, so we can just take and unpack the state without worrying about anything.

Write type code:
 while let Some(point) = rasterizer.next_point() { ... } 

It seems to me not too elegant, much more logical for this looks.
 for point in generator { ... } 

Fortunately, in order to be able to use a for loop, it is enough just to implement several types for our generator.
rust
 impl Iterator for LineRasterizer { type Item = Vec3; fn next(&mut self) -> Option<Self::Item> { self.next_point() } } 

swift
 extension LineRaster : GeneratorType { func next() -> Vector3? { return self.next_point() } } extension LineRaster : SequenceType { func generate() -> LineRaster { return self } } 


And for Swift, you need to implement as many as two protocols, while for Rust only one is enough, but it makes no fundamental difference.

Let's measure performance a little



The speed of code execution is one of the important factors when comparing different programming languages ​​and it would be foolish to write an article without measuring performance.

Naive comparison:

rust
 fn test_code(canvas: &mut Canvas) { let a = Vec3 { x: 0, y:0, z:0 }; let b = Vec3 { x: 50, y: 55, z: -20 }; let rasterizer = LineRasterizer::new(a, b); for point in rasterizer { let color = std::u32::MAX; canvas.set_pixel(point.x as usize, point.y as usize, color); } } for _ in 0..1000000 { test_code(&mut canvas) } 

swift
 func testCode(canvas: Canvas) -> () { let a = Vector3(x: 0, y:0, z:0) let b = Vector3(x: 50, y:55, z:-20) let raster = LineRaster(from: a, to: b) for point in raster { let color = UInt32.max canvas.setPixel(point.x, point.y, color: color) } } ... var myCanvas: Canvas = canvas for _ in 0..<1000000 { testCode(myCanvas) } 


We simply pass the link to the Canvas to the function under test and measure the time.
It turned out 0.86 for Rust versus 5.3 for Swift. It is likely that Rust somehow zainlaynil calls, while Swift remained at the level of dynamic dispatch. To verify this, try writing a generic function.
rust
 fn test_code_generic<T: Canvas>(canvas: &mut T) { let a = Vec3 { x: 0, y:0, z:0 }; let b = Vec3 { x: 50, y: 55, z: -20 }; let rasterizer = LineRasterizer::new(a, b); for point in rasterizer { let color = std::u32::MAX; canvas.set_pixel(point.x as usize, point.y as usize, color); } } 

swift
 func testCodeGeneric<T:Canvas>(canvas: T) -> () { let a = Vector3(x: 0, y:0, z:0) let b = Vector3(x: 50, y:55, z:-20) let raster = LineRaster(from: a, to: b) for point in raster { let color = UInt32.max canvas.setPixel(point.x, point.y, color: color) } } 

The results are 0.83 for Rust, against 4.94 for Swift, which tells us that after all, Swift managed to better optimize the code, but somewhere else there were still bottlenecks that he could not figure out.

Now we will try to pack the Canvas into the box, and for Swift to use the inout modifier, which is similar to & mut in its action.
rust
 fn test_code_boxed(canvas: &mut Box<Canvas>) { let a = Vec3 { x: 0, y:0, z:0 }; let b = Vec3 { x: 50, y: 55, z: -20 }; let rasterizer = LineRasterizer::new(a, b); for point in rasterizer { let color = std::u32::MAX; canvas.set_pixel(point.x as usize, point.y as usize, color); } } 

swift
 func testCodeInout(inout canvas: Canvas) -> () { let a = Vector3(x: 0, y:0, z:0) let b = Vector3(x: 50, y:55, z:-20) let raster = LineRaster(from: a, to: b) for point in raster { let color = UInt32.max canvas.setPixel(point.x, point.y, color: color) } } 


Results 0.91 for Rust, against 6.44 for Swift.

Boxing slowed down the execution of the code, but not so much, but the addition of inout had a significant impact on Swift. Apparently the opportunity to change the link to canvas binds the optimizer hands.

In general, Rust turns out to be more productive with a similar style of writing code.The concept of ownership allows the compiler not only to check the correctness of the program at the compilation stage, but also to avoid using the reference counter in cases where it is not needed; on the contrary, you obviously must use it if you need it. Swift, by default, uses reference counts everywhere for classes, which in many cases leads to poor performance, and even if the compiler can remove reference counting in some trivial cases, it will not guarantee that in slightly more complicated cases it will be able to do this, Rust just won't let the code compile. This indicates both the greater maturity of the compiler, and the effectiveness of the concepts embodied in it.

In conclusion, I would like to compare how Rust and Swift look like an old C ++ background.
Completely ordinary benchmark code
 #include <iostream> #include <vector> #include <cassert> #include <memory> #include <cstdlib> template <typename T> struct GenericPixmap { size_t w, h; std::vector<T> data; GenericPixmap(size_t w_, size_t h_, T fill_data = T()) : w(w_), h(h_), data(w_*h_, fill_data) { } T* operator[] (size_t i) { return &data[i*w]; } }; struct Vec3 { int x, y, z; int& operator[] (size_t i) { assert(i >=0 && i < 3); switch (i) { case 0: return x; case 1: return y; case 2: return z; default: break; } return z; } }; bool operator== (const Vec3 &a, const Vec3 &b) { return ax == bx && ay == by && az && bz; } bool operator!= (const Vec3 &a, const Vec3 &b) { return !(a == b); } struct RasterState { Vec3 step; Vec3 d; size_t majorAxis; }; struct LineRaster { Vec3 from; Vec3 to; bool firstStep; RasterState state; LineRaster(const Vec3 &f, const Vec3 &t) : from(f), to(t), firstStep(true), state{} {} bool next_point() { if (firstStep) { size_t max = 0; for (int i = 0; i < 3; ++i) { auto d = to[i] - from[i]; state.step[i] = d > 0 ? 1 : -1; d = std::abs(d); if (d > max) { max = d; state.majorAxis = i; } } firstStep = false; return true; } else { if (from == to) return false; else { auto calc_rs = [this](auto axis) { return std::abs(to[axis] - from[axis]); }; from[state.majorAxis] += state.step[state.majorAxis]; auto rs_base = calc_rs(state.majorAxis); for (int i = 0; i < 3; ++i) { auto rs = calc_rs(i); if (rs > 0 && i != state.majorAxis) { state.d[i] += rs; if (state.d[i] >= rs_base) { state.d[i] -= rs_base; from[i] += state.step[i]; } } } return true; } } } }; using Pixmap = GenericPixmap<uint32_t>; void test_code(Pixmap &pixmap) { Vec3 a { 0, 0, 0 }; Vec3 b { 50, 55, -20 }; LineRaster raster(a, b); while (raster.next_point()) { const auto &p = raster.from; pixmap[px][py] = 0xFFFFFF; } } int main(int, char **) { Pixmap pixmap(300, 300, 0); Vec3 a { 0, 0, 0 }; Vec3 b { 10, 5, -4 }; LineRaster raster(a, b); while (raster.next_point()) { const auto &p = raster.from; uint32_t color = 0xffffff; pixmap[px][py] = color; std::cout << "C++: point x:" << px << " y:" << py << " z:" << pz << " color:" << color << std::endl; } for (size_t i = 0; i < 1000000; ++i) test_code(pixmap); } 


In the process of writing, I ran into several segmentation errors and even had to work it out in lldb before getting the result.

0.79 for Rust, against 0.31 for gcc.

The result is very interesting: on the one hand Rust shows almost identical speed with clang, but on the other hand gcc simply surpassed everyone. That is, in general, the llvm platform is much sought after, but within it, Rust is already breathing clang in the back, which means that you can already safely begin to write sections that are critical for performance requirements.

The full benchmark code is on github . Suggestions for improving performance are accepted.

Unfortunately, it’s difficult to consider everything in one article, such interesting things as error handling, infrastructure, connection with C or ObjectiveC code, properties, and much more, and I hope that I can touch these things in the future.

findings



Swift' , . Rust . , . zero cost Rust' , . Swift , , Rust'.

, Swift' IDE XCode, OS X, Cocoa iOS OS X. Rust argo crates.io, , IDE GUI .

, Swift' , Grand Central Dispatch, Rust' . .

There are more vacancies at Swift now, and it is easier to teach him, but this does not mean that Rust specialists will not be in demand in the near future. I think there will be a lot of tasks in the world in which both of these languages ​​will mercilessly compete with each other and, I hope, this will benefit them.

Afterword



Uncompromising and austere Rust, He is favorable to the Bright side, Swift with the same syntactic sugar on the Dark side can start.

Update
The user Yarry sent me a pull request, which managed to speed up the Swift version by half by replacing classes with a structure. Structures in Swift behave like their counterparts from the world of C, they stand out on the stack, and for them there is no need to count references because they are copied when passed to a function. But at the same time, Swift documentation asserts that in reality copying takes place only when required. Thus, with the knowledge of such subtleties, you can significantly speed up the code. On the other hand, in Rust you have to think about it explicitly, which, although it makes the learning curve more steep, but then you have to spend less time with profiling and all sorts of experiments to optimize the code.

Source: https://habr.com/ru/post/272681/


All Articles