Notes for the programmer:
-------------------------
The underlying value describing an *Element will be an int64 when possible; otherwise it will be a *big.Int. You should attempt to gain the performance boost of using int64 versions of your computations when possible. Typical code will branch into two cases:
Take care not to modify the *big.Int returned by x.bigInt(), as this will modify the corresponding *Element (thus breaking our contract of immutability). Similarly, do not allow this *big.Int to escape into user-land.
The implementation attempts to guarantee that there is a unique 0 and 1 *Element element, returned via the functions Zero() and One(). However this isn't completely true: following good Go standard, a nil *Element behaves identically to 0. Furthermore, there's nothing stoping a user from writing:
and thus creating a new, distinct 0. Care must be taken when adding new package functions: you should use the methods x.IsZero() and x.IsOne(). It's good practice to try to reuse Zero() and One(), rather than allowing additional copies of 0 or 1 to leak out of the package. The easiest way to ensure this is to use FromInt64(n) and fromBigIntAndReuse(n) to convert your working value into a *Element suitable for returning to the user.
Performance-wise, the biggest issue is big.Int.Mul(x,y), when x and y are large. Here Magma vastly outperforms big.Int. A quick search on the web shows that there exist several different algorithms for computing x * y, with different performance characteristics that depend on the size of x and y. Implementing these different algorithms, and modifying big.Int so that it uses the appropriate algorithm in each case, is a clear future task.