Некоторое время назад я узнал о языке 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 поддерживает различные фишки, взятые от разных языков. Но думаю, что желание запихнуть в язык как можно больше всего в какой-то момент может его убить.

Комментарии (8)


  1. iskateli
    15.11.2022 11:06
    +4

    Тоже повёлся на красивый синтаксис и удобство, а под капотом оказывается не всё так гладко, вот недавняя разгромная статья по этому языку https://mawfig.github.io/2022/06/18/v-lang-in-2022.html


    1. Alekstet Автор
      15.11.2022 11:12
      +1

      Твоя? Мощно!


      1. Alekstet Автор
        17.11.2022 09:17

        Кстати, многие ошибки из статьи уже исправлены


    1. Bessnov
      15.11.2022 11:45
      +2

      Проект очень молодой, в стадии "Beta". Если сейчас погонять примеры из этой статьи, то уже не всё так плохо.
      Ну и эта хабростатья вроде про новое интересное. Совсем не обязательно бежать и заменять Vlang'ом Golang и прочее. Где-то оно может и пригодиться. Вполне может прижиться где-нибудь рядом с bash'ем как скриптовой язык.

      https://github.com/vlang/v/blob/master/doc/docs.md#cross-platform-shell-scripts-in-v

      Посмотрим как будет развиваться.


  1. mbeloshitsky
    15.11.2022 11:47
    +3

    Язык V безусловно можно обвинить в том, что он "неправильно" работает, видел я его начальные версии.

    С другой стороны нельзя не отметить, что по крайней мере его автор пишет на нем достаточно сложные штуки, далеко зашедшие. Один только ved чего стоит.

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


    1. Alekstet Автор
      15.11.2022 11:51

      Да я его не обвинял нигде, просто сделал обзор)


  1. AnthonyMikh
    16.11.2022 17:46
    +1

    А смысл обозревать язык, автор которого постоянно врёт и тупо собирает донаты под обещания?


    1. Alekstet Автор
      16.11.2022 19:23

      Ну собрать 30к звёзд на github все таки непросто)