V lang. Обзор и сравнение с Golang

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Некоторое время назад я узнал о языке V, ссылка на его репозиторий: https://github.com/vlang/v. Стало интересно поизучать его, так как он похож на Go. Меня удивило то, что на Хабре нет ни одной статьи с ним. Синтаксис языка очень схож с Go и Rust, V так же не придерживается концепции языка со стандартной иерархией типов.
Как заявляют разработчики V, он очень быстр, прост, безопасен и что его можно выучить за одни выходные. Насчет последнего я, конечно, очень сомневаюсь))) V не может соревноваться с Go по распространенности, размеру комьюнити и прочим другим вещам, присущим языкам, это и так понятно. Но взглянуть на него несомненно стоит.

Нехитрый тест на производительность

Сравним производительность V с помощью прогона по массиву и вычислению суммы элементов.

import time

fn main() {
	start_time := time.now()
	
	mut sum := i64(0)
	mut i := i64(0)
	for i <= 100000000 {
		sum += i
		i++
	}

	println(time.since(start_time).milliseconds())
	println(sum)
}

V дает результат от 125 до 181 миллисекунд против 28 на Go. Дальше рассмотрим реализацию интерфейсов и многопоточности, так как это фишки Go, отличающие его от других языков.

Интерфейсы

Интерфейсы в V реализуются неявно, разрешено встраивание, но также тут присутствует возможность хранения данных в интерфейсе. Обращение к полям данных осуществляется через точку.

struct Dog {
	animal_type string
}

fn (d Dog) speak() string {
	return 'woof'
}

struct Cat {
	animal_type string
}

fn (c Cat) speak() string {
	return 'meow'
}

interface Speaker {
	animal_type string
	speak() string
}

fn main() {
	dog := Dog{'dog'}
	cat := Cat{'cat'}

	mut arr := []Speaker{}
	arr << dog
	arr << cat
	for item in arr {
		println('a $item.animal_type says: $item.speak()')
	}
}

Через интерфейсы можно менять значение полей структуры, вроде бы обычное поведение, но чтобы это сделать надо везде указать ключевое слово mut, собственно, как и в любых других попытках изменить значение. Так как стандартно любые переменные в V неизменны. Подобное поведение в Go с использованием указателей мне кажется реализовано более удобным образом, но нужно иметь ввиду какие типы используют передачу по значению, а какие по ссылке.

//mut section
interface Bar {
mut:
	write(string, int) string
}

struct MutStruct {
mut:
	n int
}

//immut section
interface Foo {
	write(string) string
}

struct Struct {
	n int
}

fn (mut s MutStruct) write(a string, new_n int) string {
	s.n = new_n
	println(s.n) //n=5
	return a
}

fn (s Struct) write(a string) string {
	return a
}

fn main() {
	s1 := Struct{n: 2}
	mut s2 := MutStruct{n: 2}
	fn1(s1)
	fn2(mut s2, 5)
}

fn fn1(s Foo) {
    println(s.write('Foo'))
}

fn fn2(mut s Bar, n int) {
    println(s.write('Bar', n))
}

Так же интерфейсы могут иметь свои собственные методы, как и структуры в Go. В случае использования интерфейса со структурой, имеющей переопределенный метод, V вызывает базовую реализацию интерфейса, так же происходит, если интерфейс не имеет переопределенного метода. Go не позволил бы скомпилировать подобную ситуацию, выдав ошибку о необходимости добавления отсутствующего метода. В какой-то мере поведение Go более интуитивно и безопасно. В случае явного кастинга через оператор if мы можем добиться вызова метода на желаемой структуре.

interface Adoptable {}

fn (a Adoptable) speak() string {
	return 'adopt me!'
}

struct Cat {}

fn (c Cat) speak() string {
	return 'meow!'
}

struct Dog {}

fn main() {
	cat := Cat{}
	println(cat.speak()) //meow

	a := Adoptable(cat)
	println(a.speak()) //adopt me

	if a is Cat {
		println(a.speak()) //meow
	}
	
	dog := Dog{}
	b := Adoptable(dog)
	println(b.speak()) //adopt me
}

Многопоточность

Потоки в V являются объектами. Объект потока использует скрытый defer, который позволяет программе не упасть, в Go же необходимо делать это явным образом. Но это применимо только для объектов типа потока. Однако существует и более привычное использование функций без включения их в поток.

import math

fn p(a f64, b f64) {
	c := math.sqrt(a * a + b * b)
	println(c)
}

fn main() {
	mut threads := []thread{}
	threads << go p(3, 4)
	threads << go p(3, 7)
	threads.wait()
}

Если говорить о примитивах синхронизации, присутствующих в Go, это все так же имеется - Atomics, sync.WaitGroup. Синхронные и буферизированные каналы также имеются. Но размер последних не является частью типа, а должен инициализироваться следующим образом:

ch := chan int{} // unbuffered - "synchronous"
ch2 := chan f64{cap: 100} // buffer length 100

Также сохранен механизм с оператором select и закрытием каналов. Если канал не закрыт мы считываем его актуальное значение, в противном случае выполнятся действия внутри скобок.

fn main() {
	ch := chan int{}
	//ch.close()

	go fn(ch chan int) {
		ch <- 10
	}(ch)

	m := <-ch or {
		println("closed!")
	}

	println(m)
}

Каналы, а именно работа с ними, реализованы более хитрым образом. Оператор select здесь заменил традиционную нотацию ", ok", с его помощью можно проверить в каком статусе находится канал. Мы имеем возможность считать значение из канала, если канал готов, а так же знать находится он в открытом или закрытом состояние.

fn main() {
	ch := chan int{}
	go fn(ch chan int) {
		ch <- 10
	}(ch)
  
	//ch.close()
	
	if select {
		m := <- ch {
			println(m)
		}
	} {
		println("open") //open
	} else {
		println("closed") //closed
	}
}

Дополнительно у каналов имеется методы try_push и try_pop(). Но сами разработчики языка не советуют их использовать, так как они подвержены состоянию гонки.

Еще одним механизмом синхронизации является shared object. Это структура со встроенным мьютексом, которая позволяет использовать совместно общие данные.

struct SharedStruct {
mut:
	x int // data to be shared
}

fn (shared st SharedStruct) g(newValue int) {
	lock st {
		st.x = newValue
	}
}

fn main() {
	shared a := SharedStruct{
		x: 10
	}

	go a.g(15)
	go a.g(20)

	rlock a {
		println(a.x) //20
	}
}

Но использование такой структуры замедляет работу с данными, части из которых, возможно, не требуется параллельный доступ, тем не менее общая скорость работы будет снижена. Именно поэтому в Go отказались от использования SyncMap как стандартного механизма языка.

Кстати, для VSCode есть плагин, поддерживающий этот язык.

Заключение

В языке есть много других интересных концепций, которые я бы с удовольствием рассмотрел, но это выходит за рамки этой статьи. Было бы интересно почитать об особенностях структур, типе Option и других вещах.
V поддерживает различные фишки, взятые от разных языков. Но думаю, что желание запихнуть в язык как можно больше всего в какой-то момент может его убить.

Источник: https://habr.com/ru/post/699396/


Интересные статьи

Интересные статьи

Мы подготовили обзор серверной платформы для организации электронной почты, передачи голосовых данных посредством технологии VoIP, мгновенного обмена сообщениями и автоматизации совместной работы Comm...
Сегодня на рынке огромное количество курсов, предлагающих легко и быстро стать тестировщиком, но так ли это, все ли курсы одинаково полезны? Когда я решила изменить свою жизнь и снова пошла учиться, в...
Под конец года всем предзаказавшим Atari VCS на ресурсе Indie Go-Go отправили подарок. И пока все продолжают обсуждать новые консоли от Sony и Microsoft (к которым скоро ...
В современном мире Kubernetes-облаков, так или иначе, приходится сталкиваться с ошибками в программном обеспечении, которые допустил не ты и не твой коллега, но решать их придется тебе. Данная ст...
Компании переполнили рынок товаров и услуг предложениями. Разнообразие наблюдается не только в офлайне, но и в интернете. Достаточно вбить в поисковик любой запрос, чтобы получить подтверждение насыще...